// Коннектор: удобный HTTP-клиент для 1С:Предприятие 8
//
// Copyright 2017-2023 Vladimir Bondarevskiy
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
//
// URL:    https://github.com/vbondarevsky/Connector
// e-mail: vbondarevsky@gmail.com
// Версия: 2.5.0
//
// Требования: платформа 1С версии 8.3.10 и выше

#Область ПрограммныйИнтерфейс

#Область МетодыHTTP

#Область МетодыОбщегоНазначения

// Отправляет GET запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ПараметрыЗапроса - Структура, Соответствие - параметры, которые будут отправлены в URL (часть после ?):
//     * Ключ - Строка - ключ параметра в URL.
//     * Значение - Строка - значение параметра URL
//                  - Массив - сформирует строку из нескольких параметров: key=value1&key=value2 и т.д.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Get(URL, ПараметрыЗапроса = Неопределено, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(ПараметрыЗапроса, Неопределено, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "GET", URL, Параметры);

КонецФункции

// Отправляет OPTIONS запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Options(URL, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "OPTIONS", URL, Параметры);

КонецФункции

// Отправляет HEAD запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Head(URL, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "HEAD", URL, Параметры);

КонецФункции

// Отправляет POST запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Данные - Структура, Соответствие, Строка, ДвоичныеДанные - см. описание ДополнительныеПараметры.Данные.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Post(URL, Данные = Неопределено, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Данные, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "POST", URL, Параметры);

КонецФункции

// Отправляет PUT запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Данные - Структура, Соответствие, Строка, ДвоичныеДанные - см. описание ДополнительныеПараметры.Данные.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Put(URL, Данные = Неопределено, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Данные, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "PUT", URL, Параметры);

КонецФункции

// Отправляет PATCH запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Данные - Структура, Соответствие, Строка, ДвоичныеДанные - см. описание ДополнительныеПараметры.Данные.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Patch(URL, Данные = Неопределено, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Данные, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "PATCH", URL, Параметры);

КонецФункции

// Отправляет DELETE запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Данные - Структура, Соответствие, Строка, ДвоичныеДанные - см. описание ДополнительныеПараметры.Данные.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   См. ВызватьМетод
//
Функция Delete(URL, Данные = Неопределено, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Данные, Неопределено));
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, "DELETE", URL, Параметры);

КонецФункции

// Отправляет данные на указанный адрес для обработки с использованием указанного HTTP-метода.
//
// Параметры:
//   Метод - Строка - имя HTTP-метода для запроса.
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   Структура - ответ на выполненный запрос:
//     * ВремяВыполнения - Число - время выполнения запроса в миллисекундах.
//     * Cookies - Соответствие - cookies полученные с сервера.
//     * Заголовки - Соответствие - HTTP заголовки ответа.
//     * ЭтоПостоянныйРедирект - Булево - признак постоянного редиректа.
//     * ЭтоРедирект - Булево - признак редиректа.
//     * Кодировка - Строка - кодировка текста ответа.
//     * Тело - ДвоичныеДанные - тело ответа.
//     * КодСостояния - Число - код состояния ответа.
//     * URL - Строка - итоговый URL, по которому был выполнен запрос.
//
Функция ВызватьМетод(Метод, URL, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Неопределено));
	Дополнить(Параметры, ДополнительныеПараметры);
	
	Возврат ВызватьHTTPМетод(ТекущаяСессия, Метод, URL, Параметры);

КонецФункции

#КонецОбласти

#Область УпрощенныеМетодыДляРаботыСЗапросамиВФорматеJSON

// Отправляет GET запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ПараметрыЗапроса - Структура, Соответствие - параметры, которые будут отправлены в URL (часть после ?).
//     См. описание Сессия.ПараметрыЗапроса.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   Соответствие, Структура - ответ, десериализованный из JSON.
//     Параметры преобразования см. ДополнительныеПараметры.ПараметрыПреобразованияJSON.
//
Функция GetJson(URL,
				ПараметрыЗапроса = Неопределено,
				ДополнительныеПараметры = Неопределено,
				Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(ПараметрыЗапроса, Неопределено, Неопределено));
	
	ПараметрыПреобразованияJSON =
		ВыбратьЗначение(Неопределено, Параметры, "ПараметрыПреобразованияJSON", Неопределено);
	
	Возврат КакJson(ВызватьHTTPМетод(ТекущаяСессия, "GET", URL, Параметры), ПараметрыПреобразованияJSON);

КонецФункции

// Отправляет POST запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Json - Структура, Соответствие - данные, которые необходимо сериализовать в JSON.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   Соответствие, Структура - ответ, десериализованный из JSON.
//     Параметры преобразования см. ДополнительныеПараметры.ПараметрыПреобразованияJSON
//
Функция PostJson(URL, Json, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Json));
	
	ПараметрыПреобразованияJSON =
		ВыбратьЗначение(Неопределено, Параметры, "ПараметрыПреобразованияJSON", Неопределено);
	
	Возврат КакJson(ВызватьHTTPМетод(ТекущаяСессия, "POST", URL, Параметры), ПараметрыПреобразованияJSON);

КонецФункции

// Отправляет PUT запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Json - Структура, Соответствие - данные, которые необходимо сериализовать в JSON.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   Соответствие, Структура - ответ, десериализованный из JSON.
//     Параметры преобразования см. ДополнительныеПараметры.ПараметрыПреобразованияJSON
//
Функция PutJson(URL, Json, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Json));
	
	ПараметрыПреобразованияJSON =
		ВыбратьЗначение(Неопределено, Параметры, "ПараметрыПреобразованияJSON", Неопределено);
	Возврат КакJson(ВызватьHTTPМетод(ТекущаяСессия, "PUT", URL, Параметры), ПараметрыПреобразованияJSON);

КонецФункции

// Отправляет DELETE запрос
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   Json - Структура, Соответствие - данные, которые необходимо сериализовать в JSON.
//   ДополнительныеПараметры - См. НовыеПараметры
//   Сессия - См. СоздатьСессию
//
// Возвращаемое значение:
//   Соответствие, Структура - ответ, десериализованный из JSON.
//     Параметры преобразования см. ДополнительныеПараметры.ПараметрыПреобразованияJSON
//
Функция DeleteJson(URL, Json, ДополнительныеПараметры = Неопределено, Сессия = Неопределено) Экспорт

	ТекущаяСессия = ТекущаяСессия(Сессия);
	
	Параметры = НовыеПараметры();
	Дополнить(Параметры, ДополнительныеПараметры);
	Дополнить(Параметры, ПараметрыИзАргументов(Неопределено, Неопределено, Json));
	
	ПараметрыПреобразованияJSON =
		ВыбратьЗначение(Неопределено, Параметры, "ПараметрыПреобразованияJSON", Неопределено);
	Возврат КакJson(ВызватьHTTPМетод(ТекущаяСессия, "DELETE", URL, Параметры), ПараметрыПреобразованияJSON);

КонецФункции

#КонецОбласти

#Область Конструкторы

// Конструктор дополнительных параметров
//
// Возвращаемое значение:
//  Структура - позволяет задать дополнительные параметры:
//    * Заголовки - Соответствие - см. описание Сессия.Заголовки.
//    * Аутентификация - Структура - см. описание Сессия.Аутентификация
//    * Прокси - ИнтернетПрокси - см. описание Сессия.Прокси.
//    * ПараметрыЗапроса - Структура, Соответствие - см. описание Сессия.ПараметрыЗапроса.
//    * ПроверятьSSL - Булево - см. описание Сессия.ПроверятьSSL.
//    * КлиентскийСертификатSSL - СертификатКлиентаФайл, СертификатКлиентаWindows - Значение по умолчанию: Неопределено.
//    * Cookies - Массив - см. описание Сессия.Cookies.
//    * Таймаут - Число - время ожидания осуществляемого соединения и операций, в секундах.
//        Значение по умолчанию - 30 сек.
//    * РазрешитьПеренаправление - Булево - Истина - редиректы будут автоматически разрешены.
//                                          Ложь - будет выполнен только один запрос к серверу.
//    * Json - Структура, Соответствие - данные, которые необходимо сериализовать в JSON.
//    * ПараметрыПреобразованияJSON - Структура - задает параметры преобразования JSON:
//        ** ПрочитатьВСоответствие - Булево - Если Истина, чтение объекта JSON будет выполнено в Соответствие.
//             Если Ложь, объекты будут считываться в объект типа Структура.
//        ** ФорматДатыJSON - ФорматДатыJSON - формат, в котором представлена дата в строке,
//             подлежащей преобразованию.
//        ** ИменаСвойствСоЗначениямиДата -  Строка, Массив Из Строка - имена свойств JSON,
//             для которых нужно вызывать восстановление даты из строки.
//        ** ИмяФункцииВосстановления - Строка - определяет имя функции, которая будет вызывается при чтении
//            каждого свойства и должна иметь следующие параметры:
//               ** Свойство - Строка - указывается только при чтении объектов JSON
//               ** Значение - Произвольный - значение допустимого для сериализации типа
//               ** ДополнительныеПараметры - Произвольный
//            Возвращаемое значение:
//               Произвольный - значение, десериализованное из JSON. 
//        ** МодульФункцииВосстановления - Произвольный - определяет модуль, процедура которого будет использована для
//            восстановления значения.
//        ** ДополнительныеПараметрыФункцииВосстановления - Произвольный - определяет дополнительные параметры, которые
//            будут переданы в функцию восстановления значений.
//        ** ИменаСвойствДляОбработкиВосстановления - Массив - определяет массив имен свойств JSON, для которых
//            будет вызвана функция восстановления.
//        ** МаксимальнаяВложенность - Число - определяет максимальный уровень вложенности объекта JSON.
//    * ПараметрыЗаписиJSON - ПараметрыЗаписиJSON - используемые при записи объекта JSON.
//    * Данные - Строка, ДвоичныеДанные - произвольные данные, которые необходимо отправить в запросе. 
//             - Структура, Соответствие - поля формы, которые необходимо отправить в запрос:
//                 ** Ключ - Строка - имя поля.
//                 ** Значение - Строка - значение поля.
//    * Файлы - См. НовыйОтправляемыйФайл, Массив Из См. НовыйОтправляемыйФайл - файлы, к отправке
//    * МаксимальноеКоличествоПовторов - Число - количество повторных попыток соединения/отправки запроса.
//        Между попытками выполняется задержка, растущая по экспоненте.
//        Но если код состояния один из 413, 429, 503
//        и в ответе есть заголовок Retry-After,
//        то время задержки формируется из значения этого заголовка
//        Значение по умолчанию: 0 - повторы не выполняются.
//    * МаксимальноеВремяПовторов - Число - максимальное общее время (в секундах) отправки запроса с учетом повторов.
//        Значение по умолчанию: 600.
//    * КоэффициентЭкспоненциальнойЗадержки - Число - коэффициент изменения экспоненциальной задержки.
//        1 формирует последовательность задержек: 1, 2, 4, 8 и т.д.
//        2 формируется последовательность задержек: 2, 4, 8, 16 и т.д.
//        ...
//        Значение по умолчанию: 1.
//    * ПовторятьДляКодовСостояний - Неопределено - повторы будут выполняться для кодов состояний >= 500.
//                                 - Массив - повторы будут выполняться для конкретных кодов состояний.
//        Значение по умолчанию: Неопределено.
//
Функция НовыеПараметры() Экспорт

	Параметры = Новый Структура;
	Параметры.Вставить("Заголовки", Новый Соответствие);
	Параметры.Вставить("Аутентификация", Неопределено);
	Параметры.Вставить("Прокси", Неопределено);
	Параметры.Вставить("ПараметрыЗапроса", Неопределено);
	Параметры.Вставить("ПроверятьSSL", Истина);
	Параметры.Вставить("КлиентскийСертификатSSL", Неопределено);
	Параметры.Вставить("Cookies", Новый Соответствие);
	Параметры.Вставить("Таймаут", СтандартныйТаймаут());
	Параметры.Вставить("РазрешитьПеренаправление", Истина);
	Параметры.Вставить("Json", Неопределено);
	Параметры.Вставить("ПараметрыПреобразованияJSON", ПараметрыПреобразованияJSONПоУмолчанию());
	Параметры.Вставить("Данные", Неопределено);
	Параметры.Вставить("Файлы", Новый Массив);
	Параметры.Вставить("МаксимальноеКоличествоПовторов", 0);
	Параметры.Вставить("МаксимальноеВремяПовторов", 600);
	Параметры.Вставить("КоэффициентЭкспоненциальнойЗадержки", 1);
	Параметры.Вставить("ПовторятьДляКодовСостояний", Неопределено);
	
	Возврат Параметры;

КонецФункции

// Конструктор описания отправляемого файла.
//
// Параметры:
//   Имя - Строка - имя поля формы.
//   ИмяФайла - Строка - имя файла.
//   Данные - ДвоичныеДанные - двоичные данные файла.
//   Тип - Строка - MIME-тип файла
//
// Возвращаемое значение:
//  Структура:
//    * Имя - Строка - имя поля формы.
//    * ИмяФайла - Строка - имя файла.
//    * Данные - ДвоичныеДанные - двоичные данные файла.
//    * Тип - Строка - MIME-тип файла.
//    * Заголовки - Соответствие - HTTP заголовки запроса.
//
Функция НовыйОтправляемыйФайл(Имя, ИмяФайла, Данные = Неопределено, Тип = Неопределено) Экспорт
	
	Файл = Новый Структура;
	Файл.Вставить("Имя", Имя);
	Файл.Вставить("ИмяФайла", ИмяФайла);
	Файл.Вставить("Данные", ?(Данные = Неопределено, Base64Значение(""), Данные));
	Файл.Вставить("Тип", Тип);
	Файл.Вставить("Заголовки", Новый Соответствие);
	
	Возврат Файл;
	
КонецФункции

// Создает объект для хранения параметров сессии.
//
// Возвращаемое значение:
//  Структура - параметры сессии:
//    * Заголовки - Соответствие - HTTP заголовки запроса.
//    * Аутентификация - Структура - параметры аутентификации запроса.
//          ** ИспользоватьАутентификациюОС - Булево - включает использование аутентификации NTLM или Negotiate.
//                                                   Значение по умолчанию: Ложь.
//          ** Тип - Строка - тип аутентификации. Для Basic Тип можно не указывать.
//       Если Тип = Digest или Basic:
//          ** Пользователь - Строка - имя пользователя.
//          ** Пароль - Строка - пароль пользователя.
//       Если Тип = AWS4-HMAC-SHA256:
//          ** ИдентификаторКлючаДоступа - Строка - идентификатор ключа доступа.
//          ** СекретныйКлюч - Строка - секретный ключ.
//          ** Сервис - Строка - сервис, к которому выполняется подключение.
//          ** Регион - Строка - регион, к которому выполняется подключение.
//    * Прокси - ИнтернетПрокси - параметры прокси, которые будут использованы при отправке запроса.
//        Значение по умолчанию: Неопределено. При этом если в конфигурации используется БСП,
//        то значения прокси будет взято из БСП.
//    * ПараметрыЗапроса - Структура, Соответствие - параметры, которые будут отправлены в URL (часть после ?):
//        * Ключ - Строка - ключ параметра в URL.
//        * Значение - Строка - значение параметра URL
//                     - Массив - сформирует строку из нескольких параметров: key=value1&key=value2 и т.д.
//    * ПроверятьSSL - Булево - Ложь - проверка сертификата сервера не выполняется.
//                            - Истина - используется значение СертификатыУдостоверяющихЦентровОС.
//                   - СертификатыУдостоверяющихЦентровФайл - См. СертификатыУдостоверяющихЦентровФайл.
//        Значение по умолчанию: Истина.
//    * КлиентскийСертификатSSL - СертификатКлиентаФайл, СертификатКлиентаWindows - Значение по умолчанию: Неопределено.
//    * МаксимальноеКоличествоПеренаправлений - Число - максимальное количество редиректов. Защита от зацикливания.
//        Значение по умолчанию: 30
//    * Cookies - Соответствие - хранилище cookies.
//
Функция СоздатьСессию() Экспорт

	Сессия = Новый Структура;
	Сессия.Вставить("Заголовки", ЗаголовкиПоУмолчанию());
	Сессия.Вставить("Аутентификация", Неопределено);
	Сессия.Вставить("Прокси", Неопределено);
	Сессия.Вставить("ПараметрыЗапроса", Новый Структура);
	Сессия.Вставить("ПроверятьSSL", Истина);
	Сессия.Вставить("КлиентскийСертификатSSL", Неопределено);
	Сессия.Вставить("МаксимальноеКоличествоПеренаправлений", МаксимальноеКоличествоПеренаправлений());
	Сессия.Вставить("Cookies", Новый Соответствие);
	Сессия.Вставить("СлужебныеДанные", Новый Структура("ПараметрыDigest"));

	Возврат Сессия;

КонецФункции

// Пакет ответа результата вызова метода HTTP.
//
// Возвращаемое значение:
//   * Метод - Строка - имя HTTP-метода запроса
//   * URL - Строка - итоговый URL, по которому был выполнен запрос.
//   * КодСостояния - Число - Код состояния ответа..
//   * Заголовки - Соответствие - Заголовки ответа.
//   * Тело - ДвоичныеДанные - Тело ответа.
//   * Кодировка - Строка - код кодировки ответа.
//   * ВремяВыполнения - Число - время выполнения запроса в миллисекундах.
//   * ЭтоПостоянныйРедирект - Булево - указывает что это постоянный редирект.
//   * ЭтоРедирект - Булево - указывает что это редирект.
//   * Cookies - Соответствие - хранилище cookies.
//   * Ошибки - Массив Из Строка - Список ошибок возникших в ходе выполнения запроса.
//
Функция НовыйОтвет() Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Метод", "GET");
	Результат.Вставить("URL", "");
	Результат.Вставить("КодСостояния", 600); // Сетевая ошибка (>500)
	Результат.Вставить("Заголовки", Новый Соответствие);
	Результат.Вставить("Тело", Base64Значение(""));
	Результат.Вставить("Кодировка", "utf-8");
	Результат.Вставить("ВремяВыполнения", Неопределено);
	Результат.Вставить("ЭтоПостоянныйРедирект", Ложь);
	Результат.Вставить("ЭтоРедирект", Ложь);
	Результат.Вставить("Cookies", Новый Соответствие);
	Результат.Вставить("Ошибки", Новый Массив);
	
	Возврат Результат;
	
КонецФункции


// Конструктор аутентификации операционной системы
// 
// Возвращаемое значение:
//  Структура:
//    * ИспользоватьАутентификациюОС - Строка - включает использование аутентификации NTLM или Negotiate.
//
Функция НоваяАутентификацияОС() Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("ИспользоватьАутентификациюОС", Истина);
	
	Возврат Результат;
	
КонецФункции


// Конструктор аутентификации Basic
// 
// Возвращаемое значение:
//  Структура:
//    * Пользователь - Строка - имя пользователя.
//    * Пароль - Строка - пароль пользователя.
//
Функция НоваяАутентификацияBasic(Пользователь = "", Пароль = "") Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Тип", "Basic");
	Результат.Вставить("Пользователь", Пользователь);
	Результат.Вставить("Пароль", Пароль);
	
	Возврат Результат;
		
КонецФункции

// Конструктор аутентификации Digest
// 
// Возвращаемое значение:
//  Структура:
//    * Тип - Строка - Код типа аутентификации. Всегда "Digest".
//    * Пользователь - Строка - имя пользователя.
//    * Пароль - Строка - пароль пользователя.
//
Функция НоваяАутентификацияDigest(Пользователь = "", Пароль = "") Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Тип", "Digest");
	Результат.Вставить("Пользователь", Пользователь);
	Результат.Вставить("Пароль", Пароль);
	
	Возврат Результат;
	
КонецФункции

// Конструктор аутентификации AWS4-HMAC-SHA256
// 
// Возвращаемое значение:
//  Структура:
//    * Тип - Строка - Код типа аутентификации. Всегда "AWS4-HMAC-SHA256".
//    * ИдентификаторКлючаДоступа - Строка - идентификатор ключа доступа (AccessKey).
//    * СекретныйКлюч - Строка - секретный ключ (SecretKey).
//    * Сервис - Строка - сервис, к которому выполняется подключение.
//    * Регион - Строка - регион, к которому выполняется подключение.
//
Функция НоваяАутентификацияAWS4(КлючДоступа = "", СекретныйКлюч = "", Сервис = "", Регион = "") Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Тип", "AWS4-HMAC-SHA256");
	Результат.Вставить("ИдентификаторКлючаДоступа", КлючДоступа);
	Результат.Вставить("СекретныйКлюч", СекретныйКлюч);
	Результат.Вставить("Сервис", Сервис);
	Результат.Вставить("Регион", Регион);
	
	Возврат Результат;
	
КонецФункции

#КонецОбласти

#КонецОбласти

#Область ФорматыОтветов

// Возвращает ответ сервера в виде десериализованного значения JSON.
//
// Параметры:
//   Ответ - См. НовыйОтвет
//   ПараметрыПреобразованияJSON - Структура - задает параметры преобразования JSON.
//     * ПрочитатьВСоответствие - Булево - Если Истина, чтение объекта JSON будет выполнено в Соответствие.
//                                         Если Ложь, объекты будут считываться в объект типа Структура.
//     * ФорматДатыJSON - ФорматДатыJSON - формат, в котором представлена дата в строке, подлежащей преобразованию.
//     * ИменаСвойствСоЗначениямиДата -  Массив, Строка - имена свойств JSON,
//         для которых нужно вызывать восстановление даты из строки.
//     * ИмяФункцииВосстановления - Строка - определяет имя функции, которая будет вызывается при чтении
//         каждого свойства и должна иметь следующие параметры:
//            ** Свойство - Строка - указывается только при чтении объектов JSON
//            ** Значение - Произвольный - значение допустимого для сериализации типа
//            ** ДополнительныеПараметры - Произвольный
//         Возвращаемое значение:
//            Произвольный - значение, десериализованное из JSON. 
//     * МодульФункцииВосстановления - Произвольный - определяет модуль, процедура которого будет использована для
//         восстановления значения.
//     * ДополнительныеПараметрыФункцииВосстановления - Произвольный - определяет дополнительные параметры, которые
//         будут переданы в функцию восстановления значений.
//     * ИменаСвойствДляОбработкиВосстановления - Массив - определяет массив имен свойств JSON, для которых
//         будет вызвана функция восстановления.
//     * МаксимальнаяВложенность - Число - определяет максимальный уровень вложенности объекта JSON.
//
// Возвращаемое значение:
//   Соответствие - ответ сервера в виде десериализованного значения JSON.
//     Если ПараметрыПреобразования.ПрочитатьВСоответствие = Истина (по умолчанию).
//   Структура - если ПараметрыПреобразования.ПрочитатьВСоответствие = Ложь.
//
Функция КакJson(Ответ, ПараметрыПреобразованияJSON = Неопределено) Экспорт
		
	Попытка
		Возврат JsonВОбъект(РаспаковатьОтвет(Ответ), Ответ.Кодировка, ПараметрыПреобразованияJSON);
	Исключение
		ВызватьИсключение КакИсключение(Ответ, НСтр("ru = 'Ошибка при десериализации JSON.'"));
	КонецПопытки;

КонецФункции

// Возвращает ответ сервера в виде текста.
//
// Параметры:
//   Ответ - См. НовыйОтвет
//   Кодировка - Строка, КодировкаТекста - определяет кодировку текста.
//     Если значение не задано, то кодировка извлекается из Ответ.Кодировка.
//
// Возвращаемое значение:
//   Строка - ответ сервера в виде текста.
//
Функция КакТекст(Ответ, Кодировка = Неопределено) Экспорт

	Если Не ЗначениеЗаполнено(Кодировка) Тогда
		Кодировка = Ответ.Кодировка;
	КонецЕсли;

	ЧтениеТекста = Новый ЧтениеТекста(РаспаковатьОтвет(Ответ).ОткрытьПотокДляЧтения(), Кодировка);
	Текст = ЧтениеТекста.Прочитать();
	ЧтениеТекста.Закрыть();

	Если Текст = Неопределено Тогда
		Текст = "";
	КонецЕсли;

	Возврат Текст;

КонецФункции

// Возвращает ответ сервера в двоичных данных.
//
// Параметры:
//   Ответ - См. НовыйОтвет
//
// Возвращаемое значение:
//   Строка - ответ сервера в виде двоичных данных.
//
Функция КакДвоичныеДанные(Ответ) Экспорт

	Возврат РаспаковатьОтвет(Ответ);

КонецФункции

// Возвращает ответ сервера в XDTO.
//
// Параметры:
//   Ответ - См. НовыйОтвет
//   ПараметрыЧтенияXML - ПараметрыЧтенияXML - Параметры чтения, которые будут использованы при чтении данных XML
//     Подробнее см. в синтакс помощнике метод ЧтениеXML.ОткрытьПоток
//   НаборСхемXML - НаборСхемXML - Набор схем XML, используемых при проверке читаемого документа XML.
//     Если набор схем указан, но не проверен и включена проверка документа XML,
//     то будет выполнена проверка набора схем
//     Подробнее см. в синтакс помощнике метод ЧтениеXML.ОткрытьПоток
//   Кодировка - Строка, КодировкаТекста - Позволяет задать кодировку.
//     Подробнее см. в синтакс помощнике метод ЧтениеXML.ОткрытьПоток
//
// Возвращаемое значение:
//   ОбъектXDTO, СписокXDTO - тип возвращаемого значения может быть любым из поддерживаемых сериализацию в XDTO.
//
Функция КакXDTO(Ответ,
				ПараметрыЧтенияXML = Неопределено,
				НаборСхемXML = Неопределено,
				Кодировка = Неопределено) Экспорт

	Попытка
		ДвоичныеДанные = РаспаковатьОтвет(Ответ);
		
		ПотокДляЧтения = ДвоичныеДанные.ОткрытьПотокДляЧтения();
	
		Если Не ЗначениеЗаполнено(Кодировка) Тогда
			Кодировка = Ответ.Кодировка;
		КонецЕсли;
	
		ЧтениеXML = Новый ЧтениеXML;
		ЧтениеXML.ОткрытьПоток(ПотокДляЧтения, ПараметрыЧтенияXML, НаборСхемXML, Кодировка);
	
		ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
	Исключение
		ВызватьИсключение КакИсключение(Ответ, НСтр("ru = 'Ошибка при десериализации XDTO.'"));
	КонецПопытки;

	Возврат ОбъектXDTO;

КонецФункции

// Возвращает ответ сервера в виде текста предназначенного для использования в ВызватьИсключение.
//
// Параметры:
//   Ответ - См. НовыйОтвет.
//   ТекстДляПользователя - Строка - Текст пояснения причины для пользователя.
//
// Возвращаемое значение:
//   Строка - ответ сервера в виде текста исключения.
//
Функция КакИсключение(Ответ, Знач ТекстДляПользователя = Неопределено) Экспорт
	
	ТекстИсключения = СтрШаблон(
		НСтр("ru = 'HTTP %1 %2
		           |%3'"),
		Ответ.Метод,
		Ответ.URL,
		ПредставлениеКодаСостоянияHTTP(Ответ.КодСостояния)
	);
	
	ТелоОтвета = ВырезатьТекст(КакТекст(Ответ));
	
	Если Не ПустаяСтрока(ТелоОтвета) Тогда
		ТекстИсключения = ТекстИсключения + Символы.ПС + СтрШаблон(
			НСтр("ru = 'Тело ответа:
			           |%1'"),
			ТелоОтвета);
	КонецЕсли;
	
	Если Ответ.Ошибки.Количество() Тогда
		ТекстИсключения = ТекстИсключения + Символы.ПС + Символы.ПС
			+ СтрСоединить(Ответ.Ошибки, Символы.ПС + Символы.ПС);
	КонецЕсли;
	
	Если Не ПустаяСтрока(ТекстДляПользователя) Тогда
		ТекстИсключения = ТекстДляПользователя + Символы.ПС + Символы.ПС + ТекстИсключения;
	КонецЕсли;
	
	Возврат ТекстИсключения;
	
КонецФункции

#КонецОбласти

#Область ВспомогательныеФункции

// У и подготовленный URL.
// 
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//   ПараметрыЗапроса - Структура, Соответствие Из КлючИЗначение- параметры, которые будут отправлены в URL (часть после ?):
//     * Ключ - Строка - ключ параметра в URL.
//     * Значение - Строка - значение параметра URL
//                  - Массив из Строка - сформирует строку из нескольких параметров: key=value1&key=value2 и т.д.
// 
// Возвращаемое значение:
// Строка 
Функция УИ_ПодготовленныйURL(URL, ПараметрыЗапроса = Неопределено) Экспорт
	Возврат ПодготовитьURL(URL, ПараметрыЗапроса);
КонецФункции

// У и собрать адрес ресурса.
// 
// Параметры:
//  СтруктураURL - см. РазобратьURL
// Возвращаемое значение:
//  Строка - У и собрать адрес ресурса
Функция УИ_СобратьАдресРесурса(СтруктураURL) Экспорт
	Возврат СобратьАдресРесурса(СтруктураURL, Новый Структура);
КонецФункции

// Возвращает структурированное представление URL.
//
// Параметры:
//   URL - Строка - URL ресурса, к которому будет отправлен запрос.
//
// Возвращаемое значение:
//   Структура - структура URL:
//     * Схема - Строка - схема обращения к серверу (http, https).
//     * Аутентификация - Структура - параметры аутентификации:
//         ** Пользователь - Строка - имя пользователя.
//         ** Пароль - Строка - пароль пользователя.
//     * Сервер - Строка - адрес сервера.
//     * Порт - Число - порт сервера.
//     * Путь - Строка - адрес ресурса на сервере.
//     * ПараметрыЗапроса - Соответствие - параметры запроса передаваемые на сервер в URL (часть после ?):
//         ** Ключ - Строка - ключ параметра в URL.
//         ** Значение - Строка - значение параметра URL;
//                       - Массив - значения параметра (key=value1&key=value2).
//     * Фрагмент - Строка - часть URL после #.
//
Функция РазобратьURL(Знач URL) Экспорт

	Схема = "";
	Путь = "";
	Аутентификация = Новый Структура("Пользователь, Пароль", "", "");
	Сервер = "";
	Порт = "";
	Фрагмент = "";

	ДопустимыеСхемы = СтрРазделить("http,https", ",");

	URLБезСхемы = URL;
	РазбитьСтрокуПоРазделителю(Схема, URLБезСхемы, "://");
	Если ДопустимыеСхемы.Найти(НРег(Схема)) <> Неопределено Тогда
		URL = URLБезСхемы;
	Иначе
		Схема = "";
	КонецЕсли;

	Результат = РазделитьПоПервомуНайденномуРазделителю(URL, СтрРазделить("/,?,#", ","));
	URL = Результат[0];
	Если ЗначениеЗаполнено(Результат[2]) Тогда
		Путь = Результат[2] + Результат[1];
	КонецЕсли;

	АутентификацияСтрока = "";
	РазбитьСтрокуПоРазделителю(АутентификацияСтрока, URL, "@");
	Если ЗначениеЗаполнено(АутентификацияСтрока) Тогда
		АутентификацияЧасти = СтрРазделить(АутентификацияСтрока, ":");
		Аутентификация.Пользователь = АутентификацияЧасти[0];
		Если АутентификацияЧасти.Количество() > 1 Тогда
			Аутентификация.Пароль = АутентификацияЧасти[1];
		КонецЕсли;
	КонецЕсли;

	// IPv6
	РазбитьСтрокуПоРазделителю(Сервер, URL, "]");
	Если ЗначениеЗаполнено(Сервер) Тогда
		Сервер = Сервер + "]";
	КонецЕсли;

	URL = СтрЗаменить(URL, "/", "");

	РазбитьСтрокуПоРазделителю(Порт, URL, ":", Истина);

	Если Не ЗначениеЗаполнено(Сервер) Тогда
		Сервер = URL;
	КонецЕсли;

	Если ЗначениеЗаполнено(Порт) Тогда
		Порт = Число(Порт);
	Иначе
		Порт = 0;
	КонецЕсли;

	РазбитьСтрокуПоРазделителю(Фрагмент, Путь, "#", Истина);

	ПараметрыЗапроса = ЗаполнитьПараметрыЗапроса(Путь);

	Если Не ЗначениеЗаполнено(Схема) Тогда
		Схема = "http";
	КонецЕсли;

	Если Не ЗначениеЗаполнено(Путь) Тогда
		Путь = "/";
	КонецЕсли;

	Результат = Новый Структура;
	Результат.Вставить("Схема", Схема);
	Результат.Вставить("Аутентификация", Аутентификация);
	Результат.Вставить("Сервер", Сервер);
	Результат.Вставить("Порт", Порт);
	Результат.Вставить("Путь", Путь);
	Результат.Вставить("ПараметрыЗапроса", ПараметрыЗапроса);
	Результат.Вставить("Фрагмент", Фрагмент);

	Возврат Результат;

КонецФункции

// Преобразование Объекта в JSON.
//
// Параметры:
//   Объект - Произвольный - данные, которые необходимо преобразовать в JSON.
//   ПараметрыПреобразования - Структура - кодировка текста JSON. Значение по умолчанию - utf-8.
//     * ФорматДатыJSON - ФорматДатыJSON - определяет формат сериализации дат JSON-объектов.
//     * ВариантЗаписиДатыJSON - ВариантЗаписиДатыJSON - определяет вариант записи даты в формате JSON.
//     * ИмяФункцииПреобразования - Строка - функция, которая вызывается для всех свойств,
//         тип которых не поддерживает автоматическую сериализацию в JSON.
//         Функция должна быть экспортируемая и иметь следующие параметры:
//           ** Свойство - Строка - свойство структуры данных, которое не может быть
//                автоматически сериализовано в JSON.
//           ** Значение - Строка - значение свойства структуры данных, которое
//                не может быть автоматически сериализовано в JSON.
//           ** ДополнительныеПараметры - Произвольный - в этом параметре будет передан
//                ДополнительныеПараметрыФункцииПреобразования.
//           ** Отказ - Булево - отменяет операцию записи свойства.
//         Возвращаемое значение функции:
//           Произвольный - результат преобразования.
//     * МодульФункцииПреобразования - Произвольный - модуль, в котором определена функция ИмяФункцииПреобразования.
//     * ДополнительныеПараметрыФункцииПреобразования - Произвольный - параметры, которые будут переданы
//                                                                     в функцию ИмяФункцииПреобразования.
//   ПараметрыЗаписи - Структура - параметры преобразования JSON:
//     * ПереносСтрок - ПереносСтрокJSON - определяет способ переноса строк,
//         который будет использован при записи данных JSON.
//     * СимволыОтступа - Строка - определяет символы отступа, используемые при записи данных JSON.
//     * ИспользоватьДвойныеКавычки - Булево - определяет, будут ли при записи имена свойств JSON
//         записываться в двойных кавычках.
//     * ЭкранированиеСимволов - ЭкранированиеСимволовJSON - определяет используемый способ экранирования (замены)
//         символов при записи данных JSON.
//     * ЭкранироватьУгловыеСкобки - Булево - определяет, будут ли при записи экранироваться символы "<" и ">".
//     * ЭкранироватьРазделителиСтрок - Булево - определяет, будут ли экранироваться разделители строк
//         U+2028 (line-separator) и U+2029 (page-separator).
//     * ЭкранироватьАмперсанд - Булево - определяет, будет ли при записи экранироваться символ амперсанда "&".
//     * ЭкранироватьОдинарныеКавычки - Булево - определяет, будут ли экранироваться одинарные кавычки.
//     * ЭкранироватьСлеш - Булево - определяет, будет ли экранироваться слеш (косая черта) при записи значения.
//
// Возвращаемое значение:
//   Строка - объект в формате JSON.
//
Функция ОбъектВJson(Объект, Знач ПараметрыПреобразования = Неопределено, Знач ПараметрыЗаписи = Неопределено) Экспорт

	ПараметрыПреобразованияJSON = Объединить(ПараметрыПреобразованияJSONПоУмолчанию(), ПараметрыПреобразования);

	НастройкиСериализации = Новый НастройкиСериализацииJSON;
	НастройкиСериализации.ФорматСериализацииДаты = ПараметрыПреобразованияJSON.ФорматДатыJSON;
	НастройкиСериализации.ВариантЗаписиДаты = ПараметрыПреобразованияJSON.ВариантЗаписиДатыJSON;

	ПараметрыЗаписи = Объединить(ПараметрыЗаписиJSONПоУмолчанию(), ПараметрыЗаписи);

	ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(
		ПараметрыЗаписи.ПереносСтрок,
		ПараметрыЗаписи.СимволыОтступа,
		ПараметрыЗаписи.ИспользоватьДвойныеКавычки,
		ПараметрыЗаписи.ЭкранированиеСимволов,
		ПараметрыЗаписи.ЭкранироватьУгловыеСкобки,
		ПараметрыЗаписи.ЭкранироватьРазделителиСтрок,
		ПараметрыЗаписи.ЭкранироватьАмперсанд,
		ПараметрыЗаписи.ЭкранироватьОдинарныеКавычки,
		ПараметрыЗаписи.ЭкранироватьСлеш);

	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписиJSON);

	Если ПараметрыПреобразованияJSON.ИмяФункцииПреобразования <> Неопределено
		И ПараметрыПреобразованияJSON.МодульФункцииПреобразования <> Неопределено Тогда
		ЗаписатьJSON(ЗаписьJSON, Объект, НастройкиСериализации,
			ПараметрыПреобразованияJSON.ИмяФункцииПреобразования,
			ПараметрыПреобразованияJSON.МодульФункцииПреобразования,
			ПараметрыПреобразованияJSON.ДополнительныеПараметрыФункцииПреобразования);
	Иначе
		ЗаписатьJSON(ЗаписьJSON, Объект, НастройкиСериализации);
	КонецЕсли;

	Возврат ЗаписьJSON.Закрыть();

КонецФункции

// Преобразование JSON в Объект.
//
// Параметры:
//   Json - Поток, ДвоичныеДанные, Строка - данные в формате JSON.
//   Кодировка - Строка - кодировка текста JSON. Значение по умолчанию - utf-8.
//   ПараметрыПреобразования - Структура - параметры преобразования JSON:
//     * ПрочитатьВСоответствие - Булево - если Истина, чтение объекта JSON будет выполнено в Соответствие,
//         иначе в Структура.
//     * ИменаСвойствСоЗначениямиДата - Массив, Строка, ФиксированныйМассив - имена свойств JSON,
//         для которых нужно вызывать восстановление даты из строки.
//     * ФорматДатыJSON - ФорматДатыJSON - определяет формат десериализации дат JSON-объектов.
//     * ИмяФункцииВосстановления - Строка - определяет имя функции, которая будет вызывается при чтении
//         каждого свойства и должна иметь следующие параметры:
//            ** Свойство - Строка - указывается только при чтении объектов JSON
//            ** Значение - Произвольный - значение допустимого для сериализации типа
//            ** ДополнительныеПараметры - Произвольный
//         Возвращаемое значение:
//            Произвольный - значение, десериализованное из JSON. 
//     * МодульФункцииВосстановления - Произвольный - определяет модуль, процедура которого будет использована для
//         восстановления значения.
//     * ДополнительныеПараметрыФункцииВосстановления - Произвольный - определяет дополнительные параметры, которые
//         будут переданы в функцию восстановления значений.
//     * ИменаСвойствДляОбработкиВосстановления - Массив - определяет массив имен свойств JSON, для которых
//         будет вызвана функция восстановления.
//     * МаксимальнаяВложенность - Число - определяет максимальный уровень вложенности объекта JSON.
//
// Возвращаемое значение:
//   Произвольный - значение, десериализованное из JSON.
//
Функция JsonВОбъект(Json, Кодировка = "utf-8", ПараметрыПреобразования = Неопределено) Экспорт

	ПараметрыПреобразованияJSON = Объединить(ПараметрыПреобразованияJSONПоУмолчанию(), ПараметрыПреобразования);

	ЧтениеJSON = Новый ЧтениеJSON;
	Если ТипЗнч(Json) = Тип("ДвоичныеДанные") Тогда
		ЧтениеJSON.ОткрытьПоток(Json.ОткрытьПотокДляЧтения(), Кодировка);
	ИначеЕсли ТипЗнч(Json) = Тип("Строка") Тогда
		ЧтениеJSON.УстановитьСтроку(Json);
	Иначе
		ЧтениеJSON.ОткрытьПоток(Json, Кодировка);
	КонецЕсли;
	Объект = ПрочитатьJSON(
		ЧтениеJSON,
		ПараметрыПреобразованияJSON.ПрочитатьВСоответствие,
		ПараметрыПреобразованияJSON.ИменаСвойствСоЗначениямиДата,
		ПараметрыПреобразованияJSON.ФорматДатыJSON,
		ПараметрыПреобразованияJSON.ИмяФункцииВосстановления,
		ПараметрыПреобразованияJSON.МодульФункцииВосстановления,
		ПараметрыПреобразованияJSON.ДополнительныеПараметрыФункцииВосстановления,
		ПараметрыПреобразованияJSON.ИменаСвойствДляОбработкиВосстановления,
		ПараметрыПреобразованияJSON.МаксимальнаяВложенность);
	ЧтениеJSON.Закрыть();

	Возврат Объект;

КонецФункции

// Вычисляет HMAC (hash-based message authentication code).
//
// Параметры:
//   Ключ - ДвоичныеДанные - секретный ключ.
//   Данные - ДвоичныеДанные - данные, для которых нужно посчитать HMAC.
//   Алгоритм - ХешФункция - алгоритм, используемый для вычисления хеша.
//
// Возвращаемое значение:
//   ДвоичныеДанные - вычисленное значение HMAC.
//
Функция HMAC(Ключ, Данные, Алгоритм) Экспорт

	ДлинаБлока = 64;

	Если Ключ.Размер() > ДлинаБлока Тогда
		Хеширование = Новый ХешированиеДанных(Алгоритм);
		Хеширование.Добавить(Ключ);

		КлючБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Хеширование.ХешСумма);
	Иначе
		КлючБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Ключ);
	КонецЕсли;

	ИзмененныйКлюч = Новый БуферДвоичныхДанных(ДлинаБлока);
	ИзмененныйКлюч.Записать(0, КлючБуфер);

	ВнутреннийКлюч = ИзмененныйКлюч.Скопировать();
	ВнешнийКлюч = ИзмененныйКлюч;

	ВнутреннееВыравнивание = Новый БуферДвоичныхДанных(ДлинаБлока);
	ВнешнееВыравнивание = Новый БуферДвоичныхДанных(ДлинаБлока);
	Для Индекс = 0 По ДлинаБлока - 1 Цикл
		ВнутреннееВыравнивание.Установить(Индекс, 54);
		ВнешнееВыравнивание.Установить(Индекс, 92);
	КонецЦикла;

	ВнутреннееХеширование = Новый ХешированиеДанных(Алгоритм);
	ВнешнееХеширование = Новый ХешированиеДанных(Алгоритм);

	ВнутреннийКлюч.ЗаписатьПобитовоеИсключительноеИли(0, ВнутреннееВыравнивание);
	ВнешнийКлюч.ЗаписатьПобитовоеИсключительноеИли(0, ВнешнееВыравнивание);

	ВнешнееХеширование.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(ВнешнийКлюч));
	ВнутреннееХеширование.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(ВнутреннийКлюч));

	Если ЗначениеЗаполнено(Данные) Тогда
		ВнутреннееХеширование.Добавить(Данные);
	КонецЕсли;

	ВнешнееХеширование.Добавить(ВнутреннееХеширование.ХешСумма);

	Возврат ВнешнееХеширование.ХешСумма;

КонецФункции

// Возвращает структуру именованных кодов состояний HTTP.
//
// Возвращаемое значение:
//   Структура - именованные коды состояний HTTP.
//
Функция КодыСостоянияHTTP() Экспорт

	КодыСостояния = Новый Структура;
	Для Каждого Описание Из ОписанияКодовСостоянийHTTP() Цикл
		КодыСостояния.Вставить(Описание.Ключ, Описание.Код);
	КонецЦикла;

	Возврат КодыСостояния;

КонецФункции

// Возвращает текстовое представление переданного кода состояния HTTP.
//
// Параметры:
//   КодСостояния - Число - код состояния HTTP, для которого нужно получить текстовое представление.
//
// Возвращаемое значение:
//   Строка - текстовое представление кода состояния HTTP.
//
Функция ПредставлениеКодаСостоянияHTTP(КодСостояния) Экспорт

	ОписаниеКодаСостояния = Неопределено;
	Для Каждого Описание Из ОписанияКодовСостоянийHTTP() Цикл
		Если Описание.Код = КодСостояния Тогда
			ОписаниеКодаСостояния = Описание;
			Прервать;
		КонецЕсли;
	КонецЦикла;

	Если ОписаниеКодаСостояния = Неопределено Тогда
		Возврат СтрШаблон(НСтр("ru = '%1: Неизвестный код состояния HTTP'"), КодСостояния);
	Иначе
		Возврат СтрШаблон("%1: %2", ОписаниеКодаСостояния.Код, ОписаниеКодаСостояния.Описание);
	КонецЕсли;

КонецФункции

// Выполняет чтение данных из архива GZip.
//
// Параметры:
//   СжатыеДанные - ДвоичныеДанные - данные упакованные GZip.
//
// Возвращаемое значение:
//   ДвоичныеДанные - распакованные данные.
//
Функция ПрочитатьGZip(СжатыеДанные) Экспорт

	РазмерПрефиксаGZip = 10;
	РазмерПостфиксаGZip = 8;

	ЧтениеДанных = Новый ЧтениеДанных(СжатыеДанные);
	ЧтениеДанных.Пропустить(РазмерПрефиксаGZip);
	РазмерСжатыхДанных = ЧтениеДанных.ИсходныйПоток().Размер() - РазмерПрефиксаGZip - РазмерПостфиксаGZip;

	ПотокZip = Новый ПотокВПамяти(ZipРазмерLFH() + РазмерСжатыхДанных + ZipРазмерDD() + ZipРазмерCDH() + ZipРазмерEOCD());
	ЗаписьДанных = Новый ЗаписьДанных(ПотокZip);
	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(ZipLFH());
	ЧтениеДанных.КопироватьВ(ЗаписьДанных, РазмерСжатыхДанных);

	ЗаписьДанных.Закрыть();
	ЗаписьДанных = Новый ЗаписьДанных(ПотокZip);

	CRC32 = ЧтениеДанных.ПрочитатьЦелое32();
	РазмерНесжатыхДанных = ЧтениеДанных.ПрочитатьЦелое32();
	ЧтениеДанных.Закрыть();

	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(ZipDD(CRC32, РазмерСжатыхДанных, РазмерНесжатыхДанных));
	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(ZipCDH(CRC32, РазмерСжатыхДанных, РазмерНесжатыхДанных));
	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(ZipEOCD(РазмерСжатыхДанных));
	ЗаписьДанных.Закрыть();

	Возврат ПрочитатьZip(ПотокZip);

КонецФункции

// Выполняет запись данных в архив GZip.
//
// Параметры:
//   Данные - ДвоичныеДанные - исходные данные.
//
// Возвращаемое значение:
//   ДвоичныеДанные - данные упакованные GZip.
//
Функция ЗаписатьGZip(Данные) Экспорт

	ЧтениеДанных = Новый ЧтениеДанных(ЗаписатьZip(Данные));

	НачальноеСмещение = 14;
	ЧтениеДанных.Пропустить(НачальноеСмещение);
	CRC32 = ЧтениеДанных.ПрочитатьЦелое32();

	РазмерСжатыхДанных = ЧтениеДанных.ПрочитатьЦелое32();
	РазмерИсходныхДанных = ЧтениеДанных.ПрочитатьЦелое32();

	РазмерИмениФайла = ЧтениеДанных.ПрочитатьЦелое16();
	РазмерДополнительногоПоля = ЧтениеДанных.ПрочитатьЦелое16();
	ЧтениеДанных.Пропустить(РазмерИмениФайла + РазмерДополнительногоПоля);

	ПотокGZip = Новый ПотокВПамяти;
	ЗаписьДанных = Новый ЗаписьДанных(ПотокGZip);
	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(GZipHeader());
	ЧтениеДанных.КопироватьВ(ЗаписьДанных, РазмерСжатыхДанных);
	ЗаписьДанных.Закрыть();
	ЗаписьДанных = Новый ЗаписьДанных(ПотокGZip);

	ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(GZipFooter(CRC32, РазмерИсходныхДанных));

	Возврат ПотокGZip.ЗакрытьИПолучитьДвоичныеДанные();

КонецФункции

#КонецОбласти

#КонецОбласти

#Область СлужебныйПрограммныйИнтерфейс

Функция ПодготовитьЗапрос(Сессия, Метод, URL, ДополнительныеПараметры) Экспорт

	Cookies = ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Cookies", Новый Массив);
	Cookies = ОбъединитьCookies(ДозаполнитьCookie(Сессия.Cookies, URL), ДозаполнитьCookie(Cookies, URL));

	АутентификацияИзДополнительныхПараметров =
		ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Аутентификация", Новый Структура);
	ПараметрыЗапросаИзДополнительныхПараметров =
		ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "ПараметрыЗапроса", Новый Структура);
	ЗаголовкиИзДополнительныхПараметров =
		ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Заголовки", Новый Соответствие);

	Аутентификация = Объединить(Скопировать(АутентификацияИзДополнительныхПараметров), Сессия.Аутентификация);
	ПараметрыЗапроса = Объединить(Скопировать(ПараметрыЗапросаИзДополнительныхПараметров), Сессия.ПараметрыЗапроса);
	Заголовки = Объединить(Скопировать(ЗаголовкиИзДополнительныхПараметров), Сессия.Заголовки);
	ПараметрыПреобразованияJSON =
		ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "ПараметрыПреобразованияJSON", Неопределено);

	ПодготовленныйЗапрос = Новый Структура;
	ПодготовленныйЗапрос.Вставить("Cookies", Cookies);
	ПодготовленныйЗапрос.Вставить("Аутентификация", Аутентификация);
	ПодготовленныйЗапрос.Вставить("Метод", Метод);
	ПодготовленныйЗапрос.Вставить("Заголовки", Заголовки);
	ПодготовленныйЗапрос.Вставить("ПараметрыЗапроса", ПараметрыЗапроса);
	ПодготовленныйЗапрос.Вставить("URL", ПодготовитьURL(URL, ПараметрыЗапроса));
	ПодготовленныйЗапрос.Вставить("ПараметрыПреобразованияJSON", ПараметрыПреобразованияJSON);

	ПодготовитьCookies(ПодготовленныйЗапрос);

	Данные = ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Данные", Новый Структура);
	Файлы = ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Файлы", Новый Массив);
	Json = ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "Json", Неопределено);
	ПараметрыЗаписиJSON = ВыбратьЗначение(Неопределено, ДополнительныеПараметры, "ПараметрыЗаписиJSON", Неопределено);

	ПодготовитьТелоЗапроса(ПодготовленныйЗапрос, Данные, Файлы, Json, ПараметрыЗаписиJSON);
	ПодготовитьАутентификацию(ПодготовленныйЗапрос);

	Возврат ПодготовленныйЗапрос;

КонецФункции

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

#Область РаботаСHTTPЗапросами

Функция ПараметрыИзАргументов(ПараметрыЗапроса, Данные, Json)

	Результат = Новый Структура;
	Результат.Вставить("ПараметрыЗапроса", ПараметрыЗапроса);
	Результат.Вставить("Данные", Данные);
	Результат.Вставить("Json", Json);
	
	Возврат Результат;

КонецФункции

Функция ВызватьHTTPМетод(Сессия, Метод, URL, ДополнительныеПараметры)

	ПодготовленныйЗапрос = ПодготовитьЗапрос(Сессия, Метод, URL, ДополнительныеПараметры);

	НастройкиПодключения = НастройкиПодключения(Метод, URL, ДополнительныеПараметры);

	Ответ = ОтправитьЗапрос(Сессия, ПодготовленныйЗапрос, НастройкиПодключения);
	
	Если НастройкиПодключения.РазрешитьПеренаправление И Ответ.ЭтоРедирект Тогда
		// INFO: по хорошему аутентификацию нужно привести к новых параметрам, но пока будем игнорировать.
		Ответ = ПеренаправитьЗапрос(Сессия, НастройкиПодключения, ПодготовленныйЗапрос, Ответ);
	КонецЕсли;
	
	Возврат Ответ;

КонецФункции

Функция ПеренаправитьЗапрос(Сессия, НастройкиПодключения, ПодготовленныйЗапрос, ПеренаправленныйОтвет)
	
	Перенаправление = 0;
	
	Пока ПеренаправленныйОтвет.ЭтоРедирект Цикл
		
		ПодготовитьЗапросДляРедиректа(Сессия, ПодготовленныйЗапрос, ПеренаправленныйОтвет);
		
		ПеренаправленныйОтвет = ОтправитьЗапрос(Сессия, ПодготовленныйЗапрос, НастройкиПодключения);
		
		Перенаправление = Перенаправление + 1;
		
		Если Перенаправление > Сессия.МаксимальноеКоличествоПеренаправлений Тогда
			ВызватьИсключение("СлишкомМногоПеренаправлений");
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат ПеренаправленныйОтвет;
	
КонецФункции

Процедура ПодготовитьЗапросДляРедиректа(Сессия, ПодготовленныйЗапрос, ПеренаправленныйОтвет)
	
	КодыСостоянияHTTP = КодыСостоянияHTTP();
	
	НовыйURL = СформироватьНовыйURLПриПеренаправлении(ПеренаправленныйОтвет);
	
	ПодготовленныйЗапрос.URL = КодироватьСтроку(НовыйURL, СпособКодированияСтроки.URLВКодировкеURL);
	НовыйHTTPЗапрос = Новый HTTPЗапрос(СобратьАдресРесурса(РазобратьURL(НовыйURL), Новый Соответствие));
	ПереопределитьМетод(ПодготовленныйЗапрос, ПеренаправленныйОтвет);

	Если ПеренаправленныйОтвет.КодСостояния <> КодыСостоянияHTTP.ВременноеПеренаправление_307
		И ПеренаправленныйОтвет.КодСостояния <> КодыСостоянияHTTP.ПостоянноеПеренаправление_308 Тогда
		УдалитьЗаголовки(ПодготовленныйЗапрос.Заголовки, "content-length,content-type,transfer-encoding");
		НовыйHTTPЗапрос.Заголовки = ПодготовленныйЗапрос.Заголовки;
	Иначе
		ИсходныйПоток = ПодготовленныйЗапрос.HTTPЗапрос.ПолучитьТелоКакПоток();
		ИсходныйПоток.КопироватьВ(НовыйHTTPЗапрос.ПолучитьТелоКакПоток());
	КонецЕсли;
	
	ПодготовленныйЗапрос.HTTPЗапрос = НовыйHTTPЗапрос;
	УдалитьЗаголовки(ПодготовленныйЗапрос.Заголовки, "cookies");

	ПодготовленныйЗапрос.Cookies = ОбъединитьCookies(ПодготовленныйЗапрос.Cookies, Сессия.Cookies);
	ПодготовитьCookies(ПодготовленныйЗапрос);
	
КонецПроцедуры

Процедура ПодготовитьАутентификацию(ПодготовленныйЗапрос)

	ПодготовленныйЗапрос.Вставить("СобытияНаОтвет", Новый Массив);
	Если Не ЗначениеЗаполнено(ПодготовленныйЗапрос.Аутентификация) Тогда
		СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);
		Если ЗначениеЗаполнено(СтруктураURL.Аутентификация) Тогда
			ПодготовленныйЗапрос.Аутентификация = СтруктураURL.Аутентификация;
		КонецЕсли;
	КонецЕсли;

	Если ЗначениеЗаполнено(ПодготовленныйЗапрос.Аутентификация) Тогда
		Если ПодготовленныйЗапрос.Аутентификация.Свойство("Тип") Тогда
			ТипАутентификации = НРег(ПодготовленныйЗапрос.Аутентификация.Тип);
			Если ТипАутентификации = "digest" Тогда
				ПодготовленныйЗапрос.СобытияНаОтвет.Добавить("ОбработкаОтветаСКодом401");
			КонецЕсли;
			Если ТипАутентификации = "aws4-hmac-sha256" Тогда
				ПодготовитьАутентификациюAWS4(ПодготовленныйЗапрос);
			КонецЕсли;
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

Процедура ПодготовитьТелоЗапроса(ПодготовленныйЗапрос, Данные, Файлы, Json, ПараметрыЗаписиJSON)

	СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);

	HTTPЗапрос = Новый HTTPЗапрос;
	HTTPЗапрос.АдресРесурса = СобратьАдресРесурса(СтруктураURL, ПодготовленныйЗапрос.ПараметрыЗапроса);
	Если ЗначениеЗаполнено(Файлы) Тогда
		ContentType = ЗакодироватьФайлы(HTTPЗапрос, Файлы, Данные);
	ИначеЕсли ЗначениеЗаполнено(Данные) Тогда
		ContentType = "application/x-www-form-urlencoded";
		Если ТипЗнч(Данные) = Тип("ДвоичныеДанные") Тогда
			HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(Данные);
		Иначе
			Если ТипЗнч(Данные) = Тип("Строка") Тогда
				Тело = Данные;
			Иначе
				Тело = КодироватьПараметрыЗапроса(Данные);
			КонецЕсли;
			ContentType = "application/x-www-form-urlencoded; charset=utf-8";
			HTTPЗапрос.УстановитьТелоИзСтроки(Тело, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
		КонецЕсли;
	ИначеЕсли Json <> Неопределено Тогда
		ContentType = "application/json; charset=utf-8";
		СтрокаJson = ОбъектВJson(Json, ПодготовленныйЗапрос.ПараметрыПреобразованияJSON, ПараметрыЗаписиJSON);
		HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаJson, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
	Иначе
		ContentType = Неопределено;
	КонецЕсли;
	ЗначениеЗаголовка = ЗначениеЗаголовка("content-type", ПодготовленныйЗапрос.Заголовки);
	Если ЗначениеЗаголовка = Ложь И ЗначениеЗаполнено(ContentType) Тогда
		ПодготовленныйЗапрос.Заголовки.Вставить("Content-Type", ContentType);
	КонецЕсли;

	HTTPЗапрос.Заголовки = ПодготовленныйЗапрос.Заголовки;

	УпаковатьЗапрос(HTTPЗапрос);

	ПодготовленныйЗапрос.Вставить("HTTPЗапрос", HTTPЗапрос);

КонецПроцедуры

Функция ЗакодироватьФайлы(HTTPЗапрос, Файлы, Данные)

	Части = Новый Массив;
	Если ЗначениеЗаполнено(Данные) Тогда
		Для Каждого Поле Из Данные Цикл
			Части.Добавить(СоздатьПолеФормы(Новый Структура("Имя,Данные", Поле.Ключ, Поле.Значение)));
		КонецЦикла;
	КонецЕсли;
	Если ТипЗнч(Файлы) = Тип("Массив") Тогда
		Для Каждого Файл Из Файлы Цикл
			Части.Добавить(СоздатьПолеФормы(Файл));
		КонецЦикла;
	Иначе
		Части.Добавить(СоздатьПолеФормы(Файлы));
	КонецЕсли;

	Разделитель = СтрЗаменить(Новый УникальныйИдентификатор, "-", "");
	РазделительСтрок = Символы.ВК + Символы.ПС;

	ТелоЗапроса = HTTPЗапрос.ПолучитьТелоКакПоток();
	ЗаписьДанных = Новый ЗаписьДанных(ТелоЗапроса, КодировкаТекста.UTF8, ПорядокБайтов.LittleEndian, "", "", Ложь);
	Для Каждого Часть Из Части Цикл
		ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + РазделительСтрок);
		ЗаписьДанных.ЗаписатьСтроку(ЗаголовкиВСтроку(Часть.Заголовки));
		Если ТипЗнч(Часть.Данные) = Тип("ДвоичныеДанные") Тогда
			ЗаписьДанных.Записать(Часть.Данные);
		Иначе
			ЗаписьДанных.ЗаписатьСтроку(Часть.Данные);
		КонецЕсли;
		ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок);
	КонецЦикла;
	ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + "--" + РазделительСтрок);
	ЗаписьДанных.Закрыть();

	Возврат СтрШаблон("multipart/form-data; boundary=%1", Разделитель);

КонецФункции

Функция СоздатьПолеФормы(ИсходныеПараметры)

	Поле = Новый Структура("Имя,ИмяФайла,Данные,Тип,Заголовки");
	Поле.Имя = ИсходныеПараметры.Имя;
	Поле.Данные = ИсходныеПараметры.Данные;

	Поле.Тип = ЗначениеПоКлючу(ИсходныеПараметры, "Тип");
	Поле.Заголовки = ЗначениеПоКлючу(ИсходныеПараметры, "Заголовки", Новый Соответствие);
	Поле.ИмяФайла = ЗначениеПоКлючу(ИсходныеПараметры, "ИмяФайла");

	Ключ = "Content-Disposition";
	Если ЗначениеЗаголовка("content-disposition", Поле.Заголовки, Ключ) = Ложь Тогда
		Поле.Заголовки.Вставить("Content-Disposition", "form-data");
	КонецЕсли;

	Части = Новый Массив;
	Части.Добавить(Поле.Заголовки[Ключ]);
	Части.Добавить(СтрШаблон("name=""%1""", Поле.Имя));
	Если ЗначениеЗаполнено(Поле.ИмяФайла) Тогда
		Части.Добавить(СтрШаблон("filename=""%1""", Поле.ИмяФайла));
	КонецЕсли;

	Поле.Заголовки[Ключ] = СтрСоединить(Части, "; ");
	Поле.Заголовки["Content-Type"] = Поле.Тип;

	Возврат Поле;

КонецФункции

Функция ЗаполнитьПараметрыЗапроса(Путь)

	ПараметрыЗапроса = Новый Соответствие;

	Запрос = "";
	РазбитьСтрокуПоРазделителю(Запрос, Путь, "?", Истина);
	Для Каждого СтрокаКлючРавноПараметр Из СтрРазделить(Запрос, "&", Ложь) Цикл
		СтрокаКлючРавноПараметр = РаскодироватьСтроку(
			СтрокаКлючРавноПараметр, СпособКодированияСтроки.URLВКодировкеURL);

		ПозицияРавно = СтрНайти(СтрокаКлючРавноПараметр, "=");
		Если ПозицияРавно = 0 Тогда
			Ключ = СтрокаКлючРавноПараметр;
			Значение = Неопределено;
		Иначе
			Ключ = Лев(СтрокаКлючРавноПараметр, ПозицияРавно - 1);
			Значение = Сред(СтрокаКлючРавноПараметр, ПозицияРавно + 1);
		КонецЕсли;

		Если ПараметрыЗапроса.Получить(Ключ) <> Неопределено Тогда
			Если ТипЗнч(ПараметрыЗапроса[Ключ]) = Тип("Массив") Тогда
				ПараметрыЗапроса[Ключ].Добавить(Значение);
			Иначе
				Значения = Новый Массив;
				Значения.Добавить(ПараметрыЗапроса[Ключ]);
				Значения.Добавить(Значение);
				ПараметрыЗапроса[Ключ] = Значения;
			КонецЕсли;
		Иначе
			ПараметрыЗапроса.Вставить(Ключ, Значение);
		КонецЕсли;

	КонецЦикла;

	Возврат ПараметрыЗапроса;

КонецФункции

Функция КодироватьПараметрыЗапроса(ПараметрыЗапроса)

	ЧастиПараметрыЗапроса = Новый Массив;
	Для Каждого Параметр Из ПараметрыЗапроса Цикл
		Если ТипЗнч(Параметр.Значение) = Тип("Массив") Тогда
			Значения = Параметр.Значение;
		Иначе
			Значения = Новый Массив;
			Значения.Добавить(Параметр.Значение);
		КонецЕсли;

		Если Параметр.Значение = Неопределено Тогда
			ЧастиПараметрыЗапроса.Добавить(Параметр.Ключ);
		Иначе
			Для Каждого Значение Из Значения Цикл
				ЗначениеПараметра = КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL);
				ЧастиПараметрыЗапроса.Добавить(СтрШаблон("%1=%2", Параметр.Ключ, ЗначениеПараметра));
			КонецЦикла;
		КонецЕсли;
	КонецЦикла;

	Возврат СтрСоединить(ЧастиПараметрыЗапроса, "&");

КонецФункции

Процедура ПереопределитьМетод(ПодготовленныйЗапрос, Ответ)

	КодыСостоянияHTTP = КодыСостоянияHTTP();

	Метод = ПодготовленныйЗапрос.Метод;

	// http://tools.ietf.org/html/rfc7231#section-6.4.4
	Если Ответ.КодСостояния = КодыСостоянияHTTP.СмотретьДругое_303 И Метод <> "HEAD" Тогда
		Метод = "GET";
	КонецЕсли;

	// Поведение браузеров
	Если Ответ.КодСостояния = КодыСостоянияHTTP.ПеремещеноВременно_302 И Метод <> "HEAD" Тогда
		Метод = "GET";
	КонецЕсли;

	ПодготовленныйЗапрос.Метод = Метод;

КонецПроцедуры

Функция ОтправитьЗапрос(Сессия, ПодготовленныйЗапрос, Настройки)

	Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
	МиллисекундВСекунде = 1000;

	Повтор = 0;
	Длительность = 0;
	Ошибки = Новый Массив;
	
	Пока Истина Цикл
		Попытка
			Ответ = ОтправитьHTTPЗапрос(Сессия, ПодготовленныйЗапрос, Настройки);
		Исключение
			ОшибкаВыполненияЗапроса = ИнформацияОбОшибке();
			
			ТекстОшибки = СтрШаблон(
				НСтр("ru = 'HTTP %1 %2
				           |Network error:
				           |%3'"),
				ПодготовленныйЗапрос.Метод,
				ПодготовленныйЗапрос.URL,
				ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())
			);
			Ошибки.Добавить(ТекстОшибки);
		КонецПопытки;

		Повтор = Повтор + 1;
		Длительность = (ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало) / МиллисекундВСекунде;

		Если Не НеобходимоПовторитьЗапрос(Ответ, Настройки, ОшибкаВыполненияЗапроса) Тогда
			Прервать;
		КонецЕсли;

		Если Повтор > Настройки.МаксимальноеКоличествоПовторов
			ИЛИ Длительность > Настройки.МаксимальноеВремяПовторов Тогда
			Прервать;
		КонецЕсли;

		Если ОшибкаВыполненияЗапроса <> Неопределено
			ИЛИ НЕ ЭтоКодСостоянияПриКоторомНужноУчитыватьЗаголовокRetryAfter(Ответ.КодСостояния) Тогда
			ЗаголовокRetryAfter = Ложь;
		Иначе
			ЗаголовокRetryAfter = ЗначениеЗаголовка("retry-after", Ответ.Заголовки);
		КонецЕсли;
		
		Если ОшибкаВыполненияЗапроса <> Неопределено Тогда
			Ошибки.Добавить(КакИсключение(Ответ));
		КонецЕсли;
		
		ДлительностьПриостановки = РассчитатьДлительностьПриостановки(
			Повтор,
			Настройки.КоэффициентЭкспоненциальнойЗадержки,
			ЗаголовокRetryAfter,
			Настройки.МаксимальноеВремяПовторов - Длительность);
		Приостановить(ДлительностьПриостановки);
	КонецЦикла;

	Если ОшибкаВыполненияЗапроса <> Неопределено Тогда
		ВызватьИсключение(ПодробноеПредставлениеОшибки(ОшибкаВыполненияЗапроса));
	КонецЕсли;

	ЗаголовокContentType = ЗначениеЗаголовка("content-type", Ответ.Заголовки);
	Если ЗаголовокContentType = Ложь Тогда
		ЗаголовокContentType = "";
	КонецЕсли;

	ПодготовленныйОтвет = НовыйОтвет();
	ПодготовленныйОтвет.Метод = ПодготовленныйЗапрос.Метод;
	ПодготовленныйОтвет.URL = ПодготовленныйЗапрос.URL;
	ПодготовленныйОтвет.КодСостояния = Ответ.КодСостояния;
	ПодготовленныйОтвет.Заголовки = Ответ.Заголовки;
	ПодготовленныйОтвет.Тело = Ответ.ПолучитьТелоКакДвоичныеДанные();
	ПодготовленныйОтвет.Кодировка = КодировкаИзЗаголовка(ЗаголовокContentType);
	ПодготовленныйОтвет.ВремяВыполнения = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
	ПодготовленныйОтвет.ЭтоПостоянныйРедирект = ЭтоПостоянныйРедирект(Ответ.КодСостояния, Ответ.Заголовки);
	ПодготовленныйОтвет.ЭтоРедирект = ЭтоРедирект(Ответ.КодСостояния, Ответ.Заголовки);
	ПодготовленныйОтвет.Cookies = ИзвлечьCookies(Ответ.Заголовки, ПодготовленныйЗапрос.URL);
	
	Сессия.Cookies = ОбъединитьCookies(Сессия.Cookies, ПодготовленныйОтвет.Cookies);

	Возврат ПодготовленныйОтвет;

КонецФункции

Функция ОтправитьHTTPЗапрос(Сессия, ПодготовленныйЗапрос, Настройки)

	СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);
	Соединение = Соединение(СтруктураURL, ПодготовленныйЗапрос.Аутентификация, Настройки, Сессия);
	Ответ = Соединение.ВызватьHTTPМетод(ПодготовленныйЗапрос.Метод, ПодготовленныйЗапрос.HTTPЗапрос);

	Для Каждого Обработчик Из ПодготовленныйЗапрос.СобытияНаОтвет Цикл
		Если Обработчик = "ОбработкаОтветаСКодом401" Тогда
			ОбработкаОтветаСКодом401(Сессия, ПодготовленныйЗапрос, Настройки, Ответ);
		КонецЕсли;
	КонецЦикла;

	Возврат Ответ;

КонецФункции

Функция НеобходимоПовторитьЗапрос(Ответ, Настройки, ОшибкаВыполненияЗапроса)

	Если Настройки.МаксимальноеКоличествоПовторов < 1 Тогда
		ПовторитьЗапрос = Ложь;
	ИначеЕсли ОшибкаВыполненияЗапроса <> Неопределено ИЛИ ПовторятьПриКодеСостояния(Ответ.КодСостояния, Настройки) Тогда
		ПовторитьЗапрос = Истина;
	Иначе
		ЗаголовокRetryAfter = ЗначениеЗаголовка("retry-after", Ответ.Заголовки);
		ПовторитьЗапрос = ЗаголовокRetryAfter <> Ложь
			И ЭтоКодСостоянияПриКоторомНужноУчитыватьЗаголовокRetryAfter(Ответ.КодСостояния);
	КонецЕсли;

	Возврат ПовторитьЗапрос;

КонецФункции

Функция ПовторятьПриКодеСостояния(КодСостояния, Настройки)

	ПовторПриЛюбомКодеСостоянияБольшеИлиРавным500 = Настройки.ПовторятьДляКодовСостояний = Неопределено
		И КодСостояния >= КодыСостоянияHTTP().ВнутренняяОшибкаСервера_500;
	КодСостоянияСоответствуетКодуСостоянияПовтора = ТипЗнч(Настройки.ПовторятьДляКодовСостояний) = Тип("Массив")
		И Настройки.ПовторятьДляКодовСостояний.Найти(КодСостояния) <> Неопределено;
	Возврат ПовторПриЛюбомКодеСостоянияБольшеИлиРавным500 ИЛИ КодСостоянияСоответствуетКодуСостоянияПовтора;

КонецФункции

Функция ЭтоПостоянныйРедирект(КодСостояния, Заголовки)

	КодыСостоянияHTTP = КодыСостоянияHTTP();

	Возврат ЕстьЗаголовокLocation(Заголовки)
		И (КодСостояния = КодыСостоянияHTTP.ПеремещеноНавсегда_301
		ИЛИ КодСостояния = КодыСостоянияHTTP.ПостоянноеПеренаправление_308);

КонецФункции

Функция ЭтоРедирект(КодСостояния, Заголовки)

	КодыСостоянияHTTP = КодыСостоянияHTTP();

	СостоянияРедиректа = Новый Массив;
	СостоянияРедиректа.Добавить(КодыСостоянияHTTP.ПеремещеноНавсегда_301);
	СостоянияРедиректа.Добавить(КодыСостоянияHTTP.ПеремещеноВременно_302);
	СостоянияРедиректа.Добавить(КодыСостоянияHTTP.СмотретьДругое_303);
	СостоянияРедиректа.Добавить(КодыСостоянияHTTP.ВременноеПеренаправление_307);
	СостоянияРедиректа.Добавить(КодыСостоянияHTTP.ПостоянноеПеренаправление_308);

	Возврат ЕстьЗаголовокLocation(Заголовки) И СостоянияРедиректа.Найти(КодСостояния) <> Неопределено;

КонецФункции

Процедура УпаковатьЗапрос(Запрос)

	Заголовок = ЗначениеЗаголовка("content-encoding", Запрос.Заголовки);
	Если Заголовок <> Ложь Тогда
		Если НРег(Заголовок) = "gzip" Тогда
			Запрос.УстановитьТелоИзДвоичныхДанных(ЗаписатьGZip(Запрос.ПолучитьТелоКакДвоичныеДанные()));
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

Функция РаспаковатьОтвет(Ответ)

	Заголовок = ЗначениеЗаголовка("content-encoding", Ответ.Заголовки);
	Если Заголовок <> Ложь Тогда
		Если НРег(Заголовок) = "gzip" Тогда
			Возврат ПрочитатьGZip(Ответ.Тело);
		КонецЕсли;
	КонецЕсли;

	Возврат Ответ.Тело;

КонецФункции

#КонецОбласти

#Область ОбработчикиСобытий

Процедура ОбработкаОтветаСКодом401(Сессия, ПодготовленныйЗапрос, Настройки, Ответ)

	Если ЭтоРедирект(Ответ.КодСостояния, Ответ.Заголовки) Тогда
		Возврат;
	КонецЕсли;

	КодыСостоянияHTTP = КодыСостоянияHTTP();
	Если Ответ.КодСостояния < КодыСостоянияHTTP.НеверныйЗапрос_400
		ИЛИ Ответ.КодСостояния >= КодыСостоянияHTTP.ВнутренняяОшибкаСервера_500 Тогда
		Возврат;
	КонецЕсли;

	Значение = ЗначениеЗаголовка("www-authenticate", Ответ.Заголовки);
	Если Значение <> Ложь И СтрНайти(НРег(Значение), "digest") Тогда
		Позиция = СтрНайти(НРег(Значение), "digest");
		Значение = Сред(Значение, Позиция + СтрДлина("digest") + 1);
		Значение = СтрЗаменить(Значение, """", "");
		Значение = СтрЗаменить(Значение, Символы.ПС, "");

		ПараметрыDigest = Новый Структура("algorithm,realm,nonce,qop,opaque");
		Для Каждого Часть Из РазбитьСтрокуПоСтроке(Значение, ", ") Цикл
			КлючЗначение = СтрРазделить(Часть, "=");
			ПараметрыDigest.Вставить(КлючЗначение[0], КлючЗначение[1]);
		КонецЦикла;

		Сессия.СлужебныеДанные.ПараметрыDigest = ПараметрыDigest;

		ПодготовленныйЗапрос.Заголовки.Вставить("Authorization", ПодготовитьЗаголовокDigest(Сессия, ПодготовленныйЗапрос));
		ПодготовленныйЗапрос.HTTPЗапрос.Заголовки = ПодготовленныйЗапрос.Заголовки;

		Ответ = ОтправитьHTTPЗапрос(Сессия, ПодготовленныйЗапрос, Настройки);
	КонецЕсли;

КонецПроцедуры

#КонецОбласти

#Область URL

Функция ПодготовитьURL(Знач URL, ПараметрыЗапроса = Неопределено)

	URL = СокрЛ(URL);

	СтруктураURL = РазобратьURL(URL);

	ПодготовленныйURL = СтруктураURL.Схема + "://";
	Если ЗначениеЗаполнено(СтруктураURL.Аутентификация.Пользователь) Тогда
		ПодготовленныйURL = ПодготовленныйURL
			+ СтруктураURL.Аутентификация.Пользователь + ":"
			+ СтруктураURL.Аутентификация.Пароль + "@";
	КонецЕсли;
	ПодготовленныйURL = ПодготовленныйURL + СтруктураURL.Сервер;
	Если ЗначениеЗаполнено(СтруктураURL.Порт) Тогда
		ПодготовленныйURL = ПодготовленныйURL + ":" + Формат(СтруктураURL.Порт, "ЧРГ=; ЧГ=");
	КонецЕсли;

	ПодготовленныйURL = ПодготовленныйURL + СобратьАдресРесурса(СтруктураURL, ПараметрыЗапроса);

	Возврат ПодготовленныйURL;

КонецФункции

Функция СобратьАдресРесурса(СтруктураURL, ПараметрыЗапроса)

	АдресРесурса = СтруктураURL.Путь;

	ОбъединенныеПараметрыЗапроса = Объединить(Скопировать(ПараметрыЗапроса), СтруктураURL.ПараметрыЗапроса);
	Если ЗначениеЗаполнено(ОбъединенныеПараметрыЗапроса) Тогда
		АдресРесурса = АдресРесурса + "?" + КодироватьПараметрыЗапроса(ОбъединенныеПараметрыЗапроса);
	КонецЕсли;
	Если ЗначениеЗаполнено(СтруктураURL.Фрагмент) Тогда
		АдресРесурса = АдресРесурса + "#" + СтруктураURL.Фрагмент;
	КонецЕсли;

	Возврат АдресРесурса;

КонецФункции

Функция СформироватьНовыйURLПриПеренаправлении(Ответ)

	НовыйURL = ЗначениеЗаголовка("location", Ответ.Заголовки);
	НовыйURL = РаскодироватьСтроку(НовыйURL, СпособКодированияСтроки.URLВКодировкеURL);

	// Редирект без схемы
	Если СтрНачинаетсяС(НовыйURL, "//") Тогда
		СтруктураURL = РазобратьURL(Ответ.URL);
		НовыйURL = СтруктураURL.Схема + ":" + НовыйURL;
	КонецЕсли;

	СтруктураURL = РазобратьURL(НовыйURL);
	Если РазбитьСтрокуПоСтроке(НовыйURL, "://").Количество() < 2 Тогда
		СтруктураURLОтвета = РазобратьURL(Ответ.URL);
		БазовыйURL = СтрШаблон("%1://%2", СтруктураURLОтвета.Схема, СтруктураURLОтвета.Сервер);
		Если ЗначениеЗаполнено(СтруктураURLОтвета.Порт) Тогда
			БазовыйURL = БазовыйURL + ":" + Формат(СтруктураURLОтвета.Порт, "ЧРГ=; ЧГ=");
		КонецЕсли;
		Если СтрНачинаетсяС(НовыйURL, "/") Тогда
			НовыйURL = БазовыйURL + НовыйURL;
		Иначе
			ИндексПоследнегоСлеша = СтрНайти(СтруктураURLОтвета.Путь, "/", НаправлениеПоиска.СКонца);
			РодительскаяДиректория = Лев(СтруктураURLОтвета.Путь, ИндексПоследнегоСлеша);
			НовыйURL = БазовыйURL + РодительскаяДиректория + НовыйURL;
		КонецЕсли;
	КонецЕсли;

	Возврат НовыйURL;

КонецФункции

Функция ЭтоСтандартныйПорт(СтруктураURL)

	СтандартныйПортHTTP = 80;
	СтандартныйПортHTTPS = 443;

	Возврат (СтруктураURL.Схема = "http" И СтруктураURL.Порт = СтандартныйПортHTTP)
		ИЛИ (СтруктураURL.Схема = "https" И СтруктураURL.Порт = СтандартныйПортHTTPS);

КонецФункции

#КонецОбласти

#Область РаботаССоединением

Функция НастройкиПодключения(Метод, URL, ДополнительныеПараметры)

	РазрешитьПеренаправление =
		ЗначениеПоКлючу(ДополнительныеПараметры, "РазрешитьПеренаправление", ВРег(Метод) <> "HEAD");
	ПроверятьSSL = ЗначениеПоКлючу(ДополнительныеПараметры, "ПроверятьSSL", Истина);
	КлиентскийСертификатSSL = ЗначениеПоКлючу(ДополнительныеПараметры, "КлиентскийСертификатSSL");
	Прокси = ЗначениеПоКлючу(ДополнительныеПараметры, "Прокси", ПроксиПоУмолчанию(URL));
	МаксимальноеКоличествоПовторов = ЗначениеПоКлючу(ДополнительныеПараметры, "МаксимальноеКоличествоПовторов", 0);
	ПовторятьДляКодовСостояний =
		ЗначениеПоКлючу(ДополнительныеПараметры, "ПовторятьДляКодовСостояний", Неопределено);
	КоэффициентЭкспоненциальнойЗадержки =
		ЗначениеПоКлючу(ДополнительныеПараметры, "КоэффициентЭкспоненциальнойЗадержки", 1);
	МаксимальноеВремяПовторов = ЗначениеПоКлючу(ДополнительныеПараметры, "МаксимальноеВремяПовторов", 600);

	Настройки = Новый Структура;
	Настройки.Вставить("Таймаут", Таймаут(ДополнительныеПараметры));
	Настройки.Вставить("РазрешитьПеренаправление", РазрешитьПеренаправление);
	Настройки.Вставить("ПроверятьSSL", ПроверятьSSL);
	Настройки.Вставить("КлиентскийСертификатSSL", КлиентскийСертификатSSL);
	Настройки.Вставить("Прокси", Прокси);
	Настройки.Вставить("МаксимальноеКоличествоПовторов", МаксимальноеКоличествоПовторов);
	Настройки.Вставить("ПовторятьДляКодовСостояний", ПовторятьДляКодовСостояний);
	Настройки.Вставить("КоэффициентЭкспоненциальнойЗадержки", КоэффициентЭкспоненциальнойЗадержки);
	Настройки.Вставить("МаксимальноеВремяПовторов", МаксимальноеВремяПовторов);

	Возврат Настройки;

КонецФункции

Функция Соединение(ПараметрыСоединения, Аутентификация, ДополнительныеПараметры, Сессия)

	Если Не ЗначениеЗаполнено(ПараметрыСоединения.Порт) Тогда
		Если ПараметрыСоединения.Схема = "https" Тогда
			ПараметрыСоединения.Порт = 443;
		Иначе
			ПараметрыСоединения.Порт = 80;
		КонецЕсли;
	КонецЕсли;

	ЗащищенноеСоединение = Неопределено;
	Если ПараметрыСоединения.Схема = "https" Тогда
		ЗащищенноеСоединение = ОбъектЗащищенногоСоединения(ДополнительныеПараметры);
	КонецЕсли;

	Пользователь = "";
	Пароль = "";
	Если ЗначениеЗаполнено(Аутентификация) Тогда
		Если Аутентификация.Свойство("Пользователь") И Аутентификация.Свойство("Пароль") Тогда
			Пользователь = Аутентификация.Пользователь;
			Пароль = Аутентификация.Пароль;
		КонецЕсли;
	КонецЕсли;

	ИспользоватьАутентификациюОС = Аутентификация.Свойство("ИспользоватьАутентификациюОС")
		И Аутентификация.ИспользоватьАутентификациюОС = Истина;

	ПараметрыДляРасчетаИдентификатора = Новый Массив;
	ПараметрыДляРасчетаИдентификатора.Добавить(ПараметрыСоединения.Сервер);
	ПараметрыДляРасчетаИдентификатора.Добавить(ПараметрыСоединения.Порт);
	ПараметрыДляРасчетаИдентификатора.Добавить(Пользователь);
	ПараметрыДляРасчетаИдентификатора.Добавить(Пароль);
	ПараметрыДляРасчетаИдентификатора.Добавить(ДополнительныеПараметры.Таймаут);
	ПараметрыДляРасчетаИдентификатора.Добавить(ИспользоватьАутентификациюОС);
	ПараметрыДляРасчетаИдентификатора.Добавить(ЗащищенноеСоединение);
	ПараметрыДляРасчетаИдентификатора.Добавить(ДополнительныеПараметры.Прокси);

	Если Не Сессия.Свойство("СлужебныеДанные") ИЛИ ТипЗнч(Сессия.СлужебныеДанные) <> Тип("Структура") Тогда
		Сессия.Вставить("СлужебныеДанные", Новый Структура);
	КонецЕсли;
	Если Не Сессия.СлужебныеДанные.Свойство("ПулСоединений") Тогда
		Сессия.СлужебныеДанные.Вставить("ПулСоединений", Новый Соответствие);
	КонецЕсли;
	ПулСоединений = Сессия.СлужебныеДанные.ПулСоединений;

	ИдентификаторСоединения = ИдентификаторСоединения(ПараметрыДляРасчетаИдентификатора);

	Если ПулСоединений.Получить(ИдентификаторСоединения) = Неопределено Тогда
		НовоеСоединение = Новый HTTPСоединение(
			ПараметрыСоединения.Сервер,
			ПараметрыСоединения.Порт,
			Пользователь, Пароль,
			ДополнительныеПараметры.Прокси,
			ДополнительныеПараметры.Таймаут,
			ЗащищенноеСоединение,
			ИспользоватьАутентификациюОС);
		ПулСоединений.Вставить(ИдентификаторСоединения, НовоеСоединение);
	КонецЕсли;

	Возврат ПулСоединений[ИдентификаторСоединения];

КонецФункции

Функция ИдентификаторСоединения(ПараметрыСоединения)

	ПараметрыДляРасчетаИдентификатора = Новый Массив;

	Для Каждого Элемент Из ПараметрыСоединения Цикл
		ТипЭлемента = ТипЗнч(Элемент);
		Если ТипЭлемента = Тип("ИнтернетПрокси") Тогда
			ПараметрыДляРасчетаИдентификатора.Добавить(СтрСоединить(Элемент.НеИспользоватьПроксиДляАдресов, ""));
			ПараметрыДляРасчетаИдентификатора.Добавить(XMLСтрока(Элемент.НеИспользоватьПроксиДляЛокальныхАдресов));
			ПараметрыДляРасчетаИдентификатора.Добавить(Элемент.Пользователь);
			ПараметрыДляРасчетаИдентификатора.Добавить(Элемент.Пароль);
		ИначеЕсли ТипЭлемента = Тип("ЗащищенноеСоединениеOpenSSL") Тогда
			// Для упрощения будет считать, что сертификаты в рамках сессии не меняются
			Если Элемент.СертификатКлиента = Неопределено Тогда
				ПараметрыДляРасчетаИдентификатора.Добавить("");
			Иначе
				ПараметрыДляРасчетаИдентификатора.Добавить(Строка(ТипЗнч(Элемент.СертификатКлиента)));
			КонецЕсли;
			Если Элемент.СертификатыУдостоверяющихЦентров = Неопределено Тогда
				ПараметрыДляРасчетаИдентификатора.Добавить("");
			Иначе
				ПараметрыДляРасчетаИдентификатора.Добавить(Строка(ТипЗнч(Элемент.СертификатыУдостоверяющихЦентров)));
			КонецЕсли;
		Иначе
			ПараметрыДляРасчетаИдентификатора.Добавить(XMLСтрока(Элемент));
		КонецЕсли;
	КонецЦикла;

	Возврат ХешированиеДанных(ХешФункция.MD5, СтрСоединить(ПараметрыДляРасчетаИдентификатора, ""));

КонецФункции

Функция ОбъектЗащищенногоСоединения(ДополнительныеПараметры)

	Если ДополнительныеПараметры.ПроверятьSSL = Ложь Тогда
		СертификатыУЦ = Неопределено;
	ИначеЕсли ТипЗнч(ДополнительныеПараметры.ПроверятьSSL) = Тип("СертификатыУдостоверяющихЦентровФайл") Тогда
		СертификатыУЦ = ДополнительныеПараметры.ПроверятьSSL;
	Иначе
		СертификатыУЦ = Новый СертификатыУдостоверяющихЦентровОС;
	КонецЕсли;
	КлиентскийСертификат = Неопределено;
	Если ТипЗнч(ДополнительныеПараметры.КлиентскийСертификатSSL) = Тип("СертификатКлиентаФайл")
		ИЛИ ТипЗнч(ДополнительныеПараметры.КлиентскийСертификатSSL) = Тип("СертификатКлиентаWindows") Тогда
		КлиентскийСертификат = ДополнительныеПараметры.КлиентскийСертификатSSL;
	КонецЕсли;

	Возврат Новый ЗащищенноеСоединениеOpenSSL(КлиентскийСертификат, СертификатыУЦ);

КонецФункции

Функция Таймаут(ДополнительныеПараметры)

	Если ДополнительныеПараметры.Свойство("Таймаут") И ДополнительныеПараметры.Таймаут <> Неопределено Тогда
		Таймаут = ДополнительныеПараметры.Таймаут;
	Иначе
		Таймаут = СтандартныйТаймаут();
	КонецЕсли;

	Возврат Таймаут;

КонецФункции

Функция ПроксиПоУмолчанию(URL)

	ПроксиПоУмолчанию = Новый ИнтернетПрокси;
	// BSLLS:ExecuteExternalCodeInCommonModule-off
	ИмяОМПолученияФайловБСП = "ПолучениеФайловИзИнтернета";
	Если Метаданные.ОбщиеМодули.Найти(ИмяОМПолученияФайловБСП) <> Неопределено Тогда
		СтруктураURL = РазобратьURL(URL);
		Модуль = Вычислить(ИмяОМПолученияФайловБСП);
		//@skip-check empty-except-statement
		Попытка
			ПроксиПоУмолчанию = Модуль.ПолучитьПрокси(СтруктураURL.Схема);
		Исключение
		КонецПопытки;
	КонецЕсли;
	// BSLLS:ExecuteExternalCodeInCommonModule-on

	Возврат ПроксиПоУмолчанию;

КонецФункции

Функция ТекущаяСессия(Сессия)

	Если Сессия = Неопределено Тогда
		Сессия = СоздатьСессию();
	КонецЕсли;

	Возврат Сессия;

КонецФункции

#КонецОбласти

#Область Заголовки

Функция ЗаголовкиВСтроку(Заголовки)

	РазделительСтрок = Символы.ВК + Символы.ПС;
	Строки = Новый Массив;

	СортированныеЗаголовки = "Content-Disposition,Content-Type,Content-Location";
	Для Каждого Ключ Из СтрРазделить(СортированныеЗаголовки, ",") Цикл
		Значение = ЗначениеЗаголовка(Ключ, Заголовки);
		Если Значение <> Ложь И ЗначениеЗаполнено(Значение) Тогда
			Строки.Добавить(СтрШаблон("%1: %2", Ключ, Значение));
		КонецЕсли;
	КонецЦикла;

	Ключи = СтрРазделить(ВРег(СортированныеЗаголовки), ",");
	Для Каждого Заголовок Из Заголовки Цикл
		Если Ключи.Найти(ВРег(Заголовок.Ключ)) = Неопределено Тогда
			Строки.Добавить(СтрШаблон("%1: %2", Заголовок.Ключ, Заголовок.Значение));
		КонецЕсли;
	КонецЦикла;
	Строки.Добавить(РазделительСтрок);

	Возврат СтрСоединить(Строки, РазделительСтрок);

КонецФункции

Процедура УдалитьЗаголовки(Заголовки, СписокЗаголовковСтрокой)

	ЗаголовкиДляУдаления = Новый Массив;
	СписокЗаголовков = СтрРазделить(СписокЗаголовковСтрокой, ",", Ложь);
	Для Каждого Заголовок Из Заголовки Цикл
		Если СписокЗаголовков.Найти(НРег(Заголовок.Ключ)) <> Неопределено Тогда
			ЗаголовкиДляУдаления.Добавить(Заголовок.Ключ);
		КонецЕсли;
	КонецЦикла;
	Для Каждого ЗаголовокДляУдаления Из ЗаголовкиДляУдаления Цикл
		Заголовки.Удалить(ЗаголовокДляУдаления);
	КонецЦикла;

КонецПроцедуры

Функция ЕстьЗаголовокLocation(Заголовки)

	Возврат ЗначениеЗаголовка("location", Заголовки) <> Ложь;

КонецФункции

Функция КодировкаИзЗаголовка(Знач Заголовок)

	Кодировка = Неопределено;

	Заголовок = НРег(СокрЛП(Заголовок));
	ИндексРазделителя = СтрНайти(Заголовок, ";");
	Если ИндексРазделителя Тогда
		ТипСодержимого = СокрЛП(Лев(Заголовок, ИндексРазделителя - 1));
		КлючКодировки = "charset=";
		ИндексКодировки = СтрНайти(Заголовок, КлючКодировки);
		Если ИндексКодировки Тогда
			ИндексРазделителя = СтрНайти(Заголовок, ";", НаправлениеПоиска.СНачала, ИндексКодировки);
			НачальнаяПозиция = ИндексКодировки + СтрДлина(КлючКодировки);
			Если ИндексРазделителя Тогда
				ДлинаКодировки = ИндексРазделителя - НачальнаяПозиция;
			Иначе
				ДлинаКодировки = СтрДлина(Заголовок);
			КонецЕсли;
			Кодировка = Сред(Заголовок, НачальнаяПозиция, ДлинаКодировки);
			Кодировка = СтрЗаменить(Кодировка, """", "");
			Кодировка = СтрЗаменить(Кодировка, "'", "");
		КонецЕсли;
	Иначе
		ТипСодержимого = Заголовок;
	КонецЕсли;

	Если Кодировка = Неопределено И СтрНайти(ТипСодержимого, "text") Тогда
		Кодировка = "iso-8859-1";
	КонецЕсли;

	Возврат Кодировка;

КонецФункции

Функция ЗначениеЗаголовка(Заголовок, ВсеЗаголовки, Ключ = Неопределено)

	Для Каждого ОчереднойЗаголовок Из ВсеЗаголовки Цикл
		Если НРег(ОчереднойЗаголовок.Ключ) = НРег(Заголовок) Тогда
			Ключ = ОчереднойЗаголовок.Ключ;
			Возврат ОчереднойЗаголовок.Значение;
		КонецЕсли;
	КонецЦикла;

	Возврат Ложь;

КонецФункции

Функция СформироватьЗначениеЗаголовкаHost(СтруктураURL)

	Host = СтруктураURL.Сервер;
	Если ЗначениеЗаполнено(СтруктураURL.Порт) И НЕ ЭтоСтандартныйПорт(СтруктураURL) Тогда
		Host = Host + ":" + Формат(СтруктураURL.Порт, "ЧРГ=; ЧГ=");
	КонецЕсли;

	Возврат Host;

КонецФункции

Функция ПодготовитьЗаголовокDigest(Сессия, ПодготовленныйЗапрос)

	ПараметрыDigest = Сессия.СлужебныеДанные.ПараметрыDigest;

	Алгоритм = ОпределитьХешФункцию(ПараметрыDigest.algorithm);
	АлгоритмСтрокой = ВРег(ПараметрыDigest.algorithm);
	Если Алгоритм = Неопределено Тогда
		Возврат Неопределено;
	КонецЕсли;

	СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);
	Путь = СтруктураURL.Путь;
	Если ЗначениеЗаполнено(СтруктураURL.ПараметрыЗапроса) Тогда
		Путь = Путь + "?" + КодироватьПараметрыЗапроса(СтруктураURL.ПараметрыЗапроса);
	КонецЕсли;

	A1 = СтрШаблон("%1:%2:%3",
		ПодготовленныйЗапрос.Аутентификация.Пользователь,
		ПараметрыDigest.realm,
		ПодготовленныйЗапрос.Аутентификация.Пароль);
	A2 = СтрШаблон("%1:%2", ПодготовленныйЗапрос.Метод, Путь);

	HA1 = ХешированиеДанных(Алгоритм, A1);
	HA2 = ХешированиеДанных(Алгоритм, A2);

	Если Не ПараметрыDigest.Свойство("last_nonce") Тогда
		ПараметрыDigest.Вставить("last_nonce");
	КонецЕсли;

	Если ПараметрыDigest.nonce = ПараметрыDigest.last_nonce Тогда
		ПараметрыDigest.nonce_count = ПараметрыDigest.nonce_count + 1;
	Иначе
		ПараметрыDigest.Вставить("nonce_count", 1);
	КонецЕсли;

	ЗначениеNC = Формат(ПараметрыDigest.nonce_count, "ЧЦ=8; ЧВН=; ЧГ=");
	ЗначениеNonce = Лев(СтрЗаменить(НРег(Новый УникальныйИдентификатор), "-", ""), 16);

	Если АлгоритмСтрокой = "MD5-SESS" Тогда
		HA1 = ХешированиеДанных(Алгоритм, СтрШаблон("%1:%2:%3", HA1, ПараметрыDigest.nonce, ЗначениеNonce));
	КонецЕсли;

	Если Не ЗначениеЗаполнено(ПараметрыDigest.qop) Тогда
		ЗначениеResponse = ХешированиеДанных(Алгоритм, СтрШаблон("%1:%2:%3", HA1, ПараметрыDigest.nonce, HA2));
	ИначеЕсли ПараметрыDigest.qop = "auth"
		ИЛИ СтрРазделить(ПараметрыDigest.qop, ",", Ложь).Найти("auth") <> Неопределено Тогда
		ЗначениеNonceBit = СтрШаблон("%1:%2:%3:%4:%5", ПараметрыDigest.nonce, ЗначениеNC, ЗначениеNonce, "auth", HA2);
		ЗначениеResponse = ХешированиеДанных(Алгоритм, СтрШаблон("%1:%2", HA1, ЗначениеNonceBit));
	Иначе
		// INFO: auth-int не реализовано
		Возврат Неопределено;
	КонецЕсли;

	ПараметрыDigest.last_nonce = ПараметрыDigest.nonce;

	База = СтрШаблон("username=""%1"", realm=""%2"", nonce=""%3"", uri=""%4"", response=""%5""",
		ПодготовленныйЗапрос.Аутентификация.Пользователь,
		ПараметрыDigest.realm,
		ПараметрыDigest.nonce,
		Путь,
		ЗначениеResponse);
	Строки = Новый Массив;
	Строки.Добавить(База);

	Если ЗначениеЗаполнено(ПараметрыDigest.opaque) Тогда
		Строки.Добавить(СтрШаблон(", opaque=""%1""", ПараметрыDigest.opaque));
	КонецЕсли;
	Если ЗначениеЗаполнено(ПараметрыDigest.algorithm) Тогда
		Строки.Добавить(СтрШаблон(", algorithm=""%1""", ПараметрыDigest.algorithm));
	КонецЕсли;
	Если ЗначениеЗаполнено(ПараметрыDigest.qop) Тогда
		Строки.Добавить(СтрШаблон(", qop=""auth"", nc=%1, cnonce=""%2""", ЗначениеNC, ЗначениеNonce));
	КонецЕсли;

	Возврат СтрШаблон("Digest %1", СтрСоединить(Строки, ""));

КонецФункции

#КонецОбласти

#Область Cookies

Процедура ПодготовитьCookies(ПодготовленныйЗапрос)

	ЗаголовокCookie = ПодготовитьЗаголовокCookie(ПодготовленныйЗапрос);
	Если ЗначениеЗаполнено(ЗаголовокCookie) Тогда
		ПодготовленныйЗапрос.Заголовки["Cookie"] = ЗаголовокCookie;
	КонецЕсли;

КонецПроцедуры

Функция ПодготовитьЗаголовокCookie(ПодготовленныйЗапрос)

	СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);

	Cookies = Новый Массив;
	Для Каждого Cookie Из ОтобратьCookiesДляЗапроса(СтруктураURL, ПодготовленныйЗапрос.Cookies) Цикл
		Cookies.Добавить(СтрШаблон("%1=%2", Cookie.Наименование, Cookie.Значение));
	КонецЦикла;

	Возврат СтрСоединить(Cookies, "; ");

КонецФункции

Функция ОбъединитьCookies(ГлавныйИсточник, ДополнительныйИсточник)

	Cookies = Новый Соответствие;
	Для Каждого Cookie Из ПреобразоватьХранилищеCookiesВМассивCookies(ГлавныйИсточник) Цикл
		ДобавитьCookieВХранилище(Cookies, Cookie, Истина);
	КонецЦикла;
	Для Каждого Cookie Из ПреобразоватьХранилищеCookiesВМассивCookies(ДополнительныйИсточник) Цикл
		ДобавитьCookieВХранилище(Cookies, Cookie, Истина);
	КонецЦикла;

	Возврат Cookies;

КонецФункции

Функция ПреобразоватьХранилищеCookiesВМассивCookies(ХранилищеCookies)

	Cookies = Новый Массив;
	Если ТипЗнч(ХранилищеCookies) = Тип("Массив") Тогда
		Для Каждого Cookie Из ХранилищеCookies Цикл
			НоваяCookie = КонструкторCookie();
			ЗаполнитьЗначенияСвойств(НоваяCookie, Cookie);
			Cookies.Добавить(НоваяCookie);
		КонецЦикла;

		Возврат Cookies;
	КонецЕсли;

	Для Каждого Домен Из ХранилищеCookies Цикл
		Для Каждого Путь Из Домен.Значение Цикл
			Для Каждого Наименование Из Путь.Значение Цикл
				Cookies.Добавить(Наименование.Значение);
			КонецЦикла;
		КонецЦикла;
	КонецЦикла;

	Возврат Cookies;

КонецФункции

Функция ОтобратьCookiesДляЗапроса(СтруктураURL, Cookies)

	СерверВЗапросе = ДобавитьЛидирующуюТочку(СтруктураURL.Сервер);

	Результат = Новый Массив;
	Для Каждого Домен Из Cookies Цикл
		Если Не СтрЗаканчиваетсяНа(СерверВЗапросе, Домен.Ключ) Тогда
			Продолжить;
		КонецЕсли;
		Для Каждого Путь Из Домен.Значение Цикл
			Если Не СтрНачинаетсяС(СтруктураURL.Путь, Путь.Ключ) Тогда
				Продолжить;
			КонецЕсли;
			ЗаполнитьСписокОтфильтрованнымиCookies(Путь.Значение, СтруктураURL, Результат);
		КонецЦикла;
	КонецЦикла;

	Возврат Результат;

КонецФункции

Процедура ЗаполнитьСписокОтфильтрованнымиCookies(Cookies, СтруктураURL, Список)

	Для Каждого Cookie Из Cookies Цикл
		Если Cookie.Значение.ТолькоБезопасноеСоединение = Истина И СтруктураURL.Схема <> "https" Тогда
			Продолжить;
		КонецЕсли;
		// INFO: проверка срока действия игнорируется (Cookie.Значение.СрокДействия)
		// INFO: проверка порта игнорируется

		Список.Добавить(Cookie.Значение);
	КонецЦикла;

КонецПроцедуры

Функция ДозаполнитьCookie(Cookies, URL)

	СтруктураURL = РазобратьURL(URL);
	НовыеCookies = Новый Массив;
	Если ТипЗнч(Cookies) = Тип("Массив") Тогда
		Для Каждого Cookie Из Cookies Цикл
			НовыйCookie = КонструкторCookie(Cookie.Наименование, Cookie.Значение);
			ЗаполнитьЗначенияСвойств(НовыйCookie, Cookie);

			Если Не ЗначениеЗаполнено(НовыйCookie.Домен) Тогда
				НовыйCookie.Домен = СтруктураURL.Сервер;
			КонецЕсли;
			Если Не ЗначениеЗаполнено(НовыйCookie.Путь) Тогда
				НовыйCookie.Путь = "/";
			КонецЕсли;

			НовыеCookies.Добавить(НовыйCookie);
		КонецЦикла;

		Возврат НовыеCookies;
	КонецЕсли;

	Возврат Cookies;

КонецФункции

Функция ИзвлечьCookies(Заголовки, URL)

	ТекущееВремя = ТекущаяУниверсальнаяДата();
	Cookies = Новый Соответствие;
	Для Каждого ОчереднойЗаголовок Из Заголовки Цикл
		Если НРег(ОчереднойЗаголовок.Ключ) = "set-cookie" Тогда
			Для Каждого ЗаголовокCookie Из РазбитьНаОтдельныеЗаголовкиCookies(ОчереднойЗаголовок.Значение) Цикл
				Cookie = РаспарситьCookie(ЗаголовокCookie, URL, ТекущееВремя);
				Если Cookie = Неопределено Тогда
					Продолжить;
				КонецЕсли;
				Если Cookie.СрокДействия <= ТекущееВремя Тогда
					УдалитьCookieИзХранилища(Cookies, Cookie);
				Иначе
					ДобавитьCookieВХранилище(Cookies, Cookie);
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
	КонецЦикла;

	Возврат Cookies;

КонецФункции

Функция РазбитьНаОтдельныеЗаголовкиCookies(Знач Заголовок)

	Заголовки = Новый Массив;

	Если Не ЗначениеЗаполнено(Заголовок) Тогда
		Возврат Заголовки;
	КонецЕсли;

	ЗапчастиЗаголовков = СтрРазделить(Заголовок, ",", Ложь);

	ОтдельныйЗаголовок = ЗапчастиЗаголовков[0];
	Для Индекс = 1 По ЗапчастиЗаголовков.ВГраница() Цикл
		ТочкаСЗапятой = СтрНайти(ЗапчастиЗаголовков[Индекс], ";");
		Равно = СтрНайти(ЗапчастиЗаголовков[Индекс], "=");
		Если ТочкаСЗапятой И Равно И Равно < ТочкаСЗапятой Тогда
			Заголовки.Добавить(ОтдельныйЗаголовок);
			ОтдельныйЗаголовок = ЗапчастиЗаголовков[Индекс];
		Иначе
			ОтдельныйЗаголовок = ОтдельныйЗаголовок + ЗапчастиЗаголовков[Индекс];
		КонецЕсли;
	КонецЦикла;
	Заголовки.Добавить(ОтдельныйЗаголовок);

	Возврат Заголовки;

КонецФункции

Процедура ДобавитьCookieВХранилище(ХранилищеCookies, Cookie, Замещать = Ложь)

	Если ХранилищеCookies.Получить(Cookie.Домен) = Неопределено Тогда
		ХранилищеCookies[Cookie.Домен] = Новый Соответствие;
	КонецЕсли;
	Если ХранилищеCookies[Cookie.Домен].Получить(Cookie.Путь) = Неопределено Тогда
		ХранилищеCookies[Cookie.Домен][Cookie.Путь] = Новый Соответствие;
	КонецЕсли;
	Если ХранилищеCookies[Cookie.Домен][Cookie.Путь].Получить(Cookie.Наименование) = Неопределено ИЛИ Замещать Тогда
		ХранилищеCookies[Cookie.Домен][Cookie.Путь][Cookie.Наименование] = Cookie;
	КонецЕсли;

КонецПроцедуры

Процедура УдалитьCookieИзХранилища(ХранилищеCookies, Cookie)

	Если ХранилищеCookies.Получить(Cookie.Домен) <> Неопределено
		И ХранилищеCookies[Cookie.Домен].Получить(Cookie.Путь) <> Неопределено
		И ХранилищеCookies[Cookie.Домен][Cookie.Путь].Получить(Cookie.Наименование) <> Неопределено Тогда
		ХранилищеCookies[Cookie.Домен][Cookie.Путь].Удалить(Cookie.Наименование);
	КонецЕсли;

КонецПроцедуры

Функция РаспарситьCookie(Заголовок, URL, ТекущееВремя)

	Cookie = Неопределено;
	Индекс = 0;

	Для Каждого Параметр Из СтрРазделить(Заголовок, ";", Ложь) Цикл
		Индекс = Индекс + 1;
		Параметр = СокрЛП(Параметр);

		Если Индекс = 1 Тогда
			Cookie = СоздатьCookieИЗаполнитьОсновныеПараметры(Параметр);
			Продолжить;
		КонецЕсли;

		Части = СтрРазделить(Параметр, "=", Ложь);
		Ключ = НРег(Части[0]);
		Если Части.Количество() > 1 Тогда
			Значение = Части[1];
		КонецЕсли;

		Если Ключ = "domain" Тогда
			Cookie.Домен = Значение;
		ИначеЕсли Ключ = "path" Тогда
			Cookie.Путь = Значение;
		ИначеЕсли Ключ = "secure" Тогда
			Cookie.ТолькоБезопасноеСоединение = Истина;
		ИначеЕсли Ключ = "max-age" Тогда
			СрокДействияMaxAge = ТекущееВремя + ЧислоИзСтроки(Значение);
		ИначеЕсли Ключ = "expires" Тогда
			Cookie.СрокДействия = ДатаИзСтрокиRFC7231(Значение);
		Иначе
			Продолжить;
		КонецЕсли;
	КонецЦикла;
	Если ЗначениеЗаполнено(Cookie) И ЗначениеЗаполнено(СрокДействияMaxAge) Тогда
		Cookie.СрокДействия = СрокДействияMaxAge;
	КонецЕсли;

	ДозаполнитьCookieНеявнымиЗначениями(Cookie, URL);

	Возврат Cookie;

КонецФункции

Функция СоздатьCookieИЗаполнитьОсновныеПараметры(Параметр)

	Части = СтрРазделить(Параметр, "=", Ложь);
	Наименование = Части[0];
	Если Части.Количество() > 1 Тогда
		Значение = Сред(Параметр, СтрДлина(Наименование) + 2);
	КонецЕсли;

	Возврат КонструкторCookie(Наименование, Значение);

КонецФункции

Процедура ДозаполнитьCookieНеявнымиЗначениями(Cookie, URL)

	Если Cookie = Неопределено Тогда
		Возврат;
	КонецЕсли;

	СтруктураURL = РазобратьURL(URL);
	Если Не ЗначениеЗаполнено(Cookie.Домен) Тогда
		Cookie.Домен = СтруктураURL.Сервер;
	КонецЕсли;
	Если Не ЗначениеЗаполнено(Cookie.Порт) И ЗначениеЗаполнено(СтруктураURL.Порт) Тогда
		Cookie.Порт = СтруктураURL.Порт;
	КонецЕсли;
	Если Не ЗначениеЗаполнено(Cookie.Путь) Тогда
		ПозицияПоследнегоСлеша = СтрНайти(СтруктураURL.Путь, "/", НаправлениеПоиска.СКонца);
		Если ПозицияПоследнегоСлеша <= 1 Тогда
			Cookie.Путь = "/";
		Иначе
			Cookie.Путь = Лев(СтруктураURL.Путь, ПозицияПоследнегоСлеша - 1);
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

Функция КонструкторCookie(Наименование = "", Значение = Неопределено)

	НовыйCookie = Новый Структура;
	НовыйCookie.Вставить("Наименование", Наименование);
	НовыйCookie.Вставить("Значение", Значение);
	НовыйCookie.Вставить("Домен", "");
	НовыйCookie.Вставить("Путь", "");
	НовыйCookie.Вставить("Порт");
	НовыйCookie.Вставить("СрокДействия", '39990101');
	НовыйCookie.Вставить("ТолькоБезопасноеСоединение");

	Возврат НовыйCookie;

КонецФункции

#КонецОбласти

#Область ПараметрыРаботыСJSON

// Преобразует значение типа к типу, сериализация которого поддерживается.
//
// Параметры:
//   Свойство - Строка - имя свойства, если выполняется запись структуры или соответствия.
//   Значение - Произвольный - исходное значение.
//   ДополнительныеПараметры - Произвольный - дополнительные параметры, которые указаны в вызове метода ЗаписатьJSON.
//   Отказ - Булево - отказ от записи свойства.
//
// Возвращаемое значение:
//   Произвольный - см. типы ЗаписатьJSON.
//
Функция ПреобразованиеJson(Свойство, Значение, ДополнительныеПараметры, Отказ) Экспорт

	Если ТипЗнч(Значение) = Тип("УникальныйИдентификатор") Тогда
		Возврат Строка(Значение);
	ИначеЕсли ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
		Возврат ПолучитьBase64СтрокуИзДвоичныхДанных(Значение);
	Иначе
		// Если значение не поддерживает сериализацию в JSON, то будет выброшено исключение
		Возврат Значение;
	КонецЕсли;

КонецФункции

// Восстанавливает значение типа, десериализация которого не поддерживается.
//
// Параметры:
//   Свойство - Строка - имя свойства, значение которого нужно восстановить.
//   Значение - Строка - значение, которое нужно восстановить.
//   ТипыСвойств - Соответствие - типы свойств, которые нужно восстановить.
//     * Ключ - Строка - имя свойства. Равно значению параметра Свойство.
//     * Значение - Тип - исходный тип значения.
//
// Возвращаемое значение:
//   Произвольный - восстановленное значение.
//
Функция ВосстановлениеJson(Свойство, Значение, ТипыСвойств) Экспорт

	ТипСвойства = ТипыСвойств.Получить(Свойство);
	Если ТипСвойства = Тип("УникальныйИдентификатор") Тогда
		Возврат Новый УникальныйИдентификатор(Значение);
	ИначеЕсли ТипСвойства = Тип("ДвоичныеДанные") Тогда
		Возврат ПолучитьДвоичныеДанныеИзBase64Строки(Значение);
	Иначе
		Возврат Значение;
	КонецЕсли;

КонецФункции

#КонецОбласти

#Область АутентификацияAWS4

Функция КлючПодписиAWS4(СекретныйКлюч, Дата, Регион, Сервис)

	КлючДата = ПодписатьСообщениеHMAC("AWS4" + СекретныйКлюч, Дата);
	КлючРегион = ПодписатьСообщениеHMAC(КлючДата, Регион);
	КлючСервис = ПодписатьСообщениеHMAC(КлючРегион, Сервис);

	Возврат ПодписатьСообщениеHMAC(КлючСервис, "aws4_request");

КонецФункции

Функция ПодписатьСообщениеHMAC(Знач Ключ, Знач Сообщение, Знач Алгоритм = Неопределено)

	Если Алгоритм = Неопределено Тогда
		Алгоритм = ХешФункция.SHA256;
	КонецЕсли;

	Если ТипЗнч(Ключ) = Тип("Строка") Тогда
		Ключ = ПолучитьДвоичныеДанныеИзСтроки(Ключ, КодировкаТекста.UTF8, Ложь);
	КонецЕсли;
	Если ТипЗнч(Сообщение) = Тип("Строка") Тогда
		Сообщение = ПолучитьДвоичныеДанныеИзСтроки(Сообщение, КодировкаТекста.UTF8, Ложь);
	КонецЕсли;

	Возврат HMAC(Ключ, Сообщение, Алгоритм);

КонецФункции

Процедура ПодготовитьАутентификациюAWS4(ПодготовленныйЗапрос)

	ЗначениеЗаголовка = ЗначениеЗаголовка("x-amz-date", ПодготовленныйЗапрос.Заголовки);
	Если ЗначениеЗаголовка <> Ложь Тогда
		ТекущееВремя = Дата(СтрЗаменить(СтрЗаменить(ЗначениеЗаголовка, "T", ""), "Z", ""));
	Иначе
		ТекущееВремя = ТекущаяУниверсальнаяДата();
	КонецЕсли;
	ПодготовленныйЗапрос.Заголовки["x-amz-date"] = Формат(ТекущееВремя, "ДФ=yyyyMMddTHHmmssZ");
	ОбластьДействияДата = Формат(ТекущееВремя, "ДФ=yyyyMMdd");

	ПодготовленныйЗапрос.Заголовки["x-amz-content-sha256"] =
		ХешированиеДанных(ХешФункция.SHA256, ПодготовленныйЗапрос.HTTPЗапрос.ПолучитьТелоКакПоток());

	СтруктураURL = РазобратьURL(ПодготовленныйЗапрос.URL);

	КаноническиеЗаголовки = КаноническиеЗаголовкиAWS4(ПодготовленныйЗапрос.Заголовки, СтруктураURL);

	КаноническийПуть = СтруктураURL.Путь;
	КаноническиеПараметрыЗапроса = КаноническиеПараметрыЗапросаAWS4(СтруктураURL.ПараметрыЗапроса);

	ЧастиЗапроса = Новый Массив;
	ЧастиЗапроса.Добавить(ПодготовленныйЗапрос.Метод);
	ЧастиЗапроса.Добавить(КаноническийПуть);
	ЧастиЗапроса.Добавить(КаноническиеПараметрыЗапроса);
	ЧастиЗапроса.Добавить(КаноническиеЗаголовки.КаноническиеЗаголовки);
	ЧастиЗапроса.Добавить(КаноническиеЗаголовки.ПодписываемыеЗаголовки);
	ЧастиЗапроса.Добавить(ПодготовленныйЗапрос.Заголовки["x-amz-content-sha256"]);
	КаноническийЗапрос = СтрСоединить(ЧастиЗапроса, Символы.ПС);

	ЧастиОбластиДействия = Новый Массив;
	ЧастиОбластиДействия.Добавить(ОбластьДействияДата);
	ЧастиОбластиДействия.Добавить(ПодготовленныйЗапрос.Аутентификация.Регион);
	ЧастиОбластиДействия.Добавить(ПодготовленныйЗапрос.Аутентификация.Сервис);
	ЧастиОбластиДействия.Добавить("aws4_request");
	ОбластьДействия = СтрСоединить(ЧастиОбластиДействия, "/");

	ЧастиСтрокиДляПодписи = Новый Массив;
	ЧастиСтрокиДляПодписи.Добавить(ПодготовленныйЗапрос.Аутентификация.Тип);
	ЧастиСтрокиДляПодписи.Добавить(ПодготовленныйЗапрос.Заголовки["x-amz-date"]);
	ЧастиСтрокиДляПодписи.Добавить(ОбластьДействия);
	ЧастиСтрокиДляПодписи.Добавить(ХешированиеДанных(ХешФункция.SHA256, КаноническийЗапрос));
	СтрокаДляПодписи = СтрСоединить(ЧастиСтрокиДляПодписи, Символы.ПС);

	Ключ = КлючПодписиAWS4(
		ПодготовленныйЗапрос.Аутентификация.СекретныйКлюч,
		ОбластьДействияДата,
		ПодготовленныйЗапрос.Аутентификация.Регион,
		ПодготовленныйЗапрос.Аутентификация.Сервис);
	Подпись = НРег(ПолучитьHexСтрокуИзДвоичныхДанных(ПодписатьСообщениеHMAC(Ключ, СтрокаДляПодписи)));

	ПодготовленныйЗапрос.Заголовки["Authorization"] = СтрШаблон(
		"%1 Credential=%2/%3, SignedHeaders=%4, Signature=%5",
		ПодготовленныйЗапрос.Аутентификация.Тип,
		ПодготовленныйЗапрос.Аутентификация.ИдентификаторКлючаДоступа,
		ОбластьДействия,
		КаноническиеЗаголовки.ПодписываемыеЗаголовки,
		Подпись);

	ПодготовленныйЗапрос.HTTPЗапрос.Заголовки = ПодготовленныйЗапрос.Заголовки;

КонецПроцедуры

Функция КаноническиеЗаголовкиAWS4(Заголовки, СтруктураURL)

	Список = Новый СписокЗначений;

	ЗаголовокHostЕстьВЗапросе = Ложь;
	ЗаголовкиПоУмолчанию = ЗаголовкиПоУмолчаниюAWS4();
	Для Каждого ОчереднойЗаголовок Из Заголовки Цикл
		Заголовок = НРег(ОчереднойЗаголовок.Ключ);
		Если ЗаголовкиПоУмолчанию.Исключения.Найти(Заголовок) <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		ЗаголовокHostЕстьВЗапросе = Макс(ЗаголовокHostЕстьВЗапросе, Заголовок = "host");

		Если ЗаголовкиПоУмолчанию.Равно.Найти(Заголовок) <> Неопределено Тогда
			Список.Добавить(Заголовок, СокрЛП(ОчереднойЗаголовок.Значение));
		Иначе
			Для Каждого Префикс Из ЗаголовкиПоУмолчанию.НачинаетсяС Цикл
				Если СтрНачинаетсяС(Заголовок, Префикс) Тогда
					Список.Добавить(Заголовок, СокрЛП(ОчереднойЗаголовок.Значение));
					Прервать;
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
	КонецЦикла;

	Если Не ЗаголовокHostЕстьВЗапросе Тогда
		Список.Добавить("host", СформироватьЗначениеЗаголовкаHost(СтруктураURL));
	КонецЕсли;

	Список.СортироватьПоЗначению(НаправлениеСортировки.Возр);

	КаноническиеЗаголовки = Новый Массив;
	ПодписываемыеЗаголовки = Новый Массив;
	Для Каждого ЭлементСписка Из Список Цикл
		КаноническиеЗаголовки.Добавить(ЭлементСписка.Значение + ":" + ЭлементСписка.Представление);
		ПодписываемыеЗаголовки.Добавить(ЭлементСписка.Значение);
	КонецЦикла;
	КаноническиеЗаголовки.Добавить("");

	КаноническиеЗаголовки = СтрСоединить(КаноническиеЗаголовки, Символы.ПС);
	ПодписываемыеЗаголовки = СтрСоединить(ПодписываемыеЗаголовки, ";");
	Возврат Новый Структура(
		"КаноническиеЗаголовки, ПодписываемыеЗаголовки",
		КаноническиеЗаголовки, ПодписываемыеЗаголовки);

КонецФункции

Функция КаноническиеПараметрыЗапросаAWS4(ПараметрыЗапроса)

	Список = Новый СписокЗначений;
	Для Каждого ОчереднойПараметрЗапроса Из ПараметрыЗапроса Цикл
		Список.Добавить(ОчереднойПараметрЗапроса.Ключ, СокрЛП(ОчереднойПараметрЗапроса.Значение));
	КонецЦикла;
	Список.СортироватьПоЗначению(НаправлениеСортировки.Возр);

	КаноническиеПараметры = Новый Массив;
	Для Каждого ЭлементСписка Из Список Цикл
		ЗначениеПараметра = КодироватьСтроку(ЭлементСписка.Представление, СпособКодированияСтроки.КодировкаURL);
		КаноническиеПараметры.Добавить(ЭлементСписка.Значение + "=" + ЗначениеПараметра);
	КонецЦикла;

	Возврат СтрСоединить(КаноническиеПараметры, "&");

КонецФункции

Функция ЗаголовкиПоУмолчаниюAWS4()

	Заголовки = Новый Структура;
	Заголовки.Вставить("Равно", СтрРазделить("host,content-type,date", ","));
	Заголовки.Вставить("НачинаетсяС", СтрРазделить("x-amz-", ","));
	Заголовки.Вставить("Исключения", СтрРазделить("x-amz-client-context", ","));

	Возврат Заголовки;

КонецФункции

#КонецОбласти

#Область КодированиеДекодированиеДанных

#Область СлужебныеСтруктурыZip

// Описание структур см. здесь https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

Функция ZipРазмерLFH()

	Возврат 34;

КонецФункции

Функция ZipРазмерDD()

	Возврат 16;

КонецФункции

Функция ZipРазмерCDH()

	Возврат 50;

КонецФункции

Функция ZipРазмерEOCD()

	Возврат 22;

КонецФункции

Функция ZipLFH()

	// Local file header
	Буфер = Новый БуферДвоичныхДанных(ZipРазмерLFH());
	Буфер.ЗаписатьЦелое32(0, 67324752); // signature 0x04034b50
	Буфер.ЗаписатьЦелое16(4, 20);       // version
	Буфер.ЗаписатьЦелое16(6, 10);       // bit flags
	Буфер.ЗаписатьЦелое16(8, 8);        // compression method
	Буфер.ЗаписатьЦелое16(10, 0);       // time
	Буфер.ЗаписатьЦелое16(12, 0);       // date
	Буфер.ЗаписатьЦелое32(14, 0);       // crc-32
	Буфер.ЗаписатьЦелое32(18, 0);       // compressed size
	Буфер.ЗаписатьЦелое32(22, 0);       // uncompressed size
	Буфер.ЗаписатьЦелое16(26, 4);       // filename legth - "data"
	Буфер.ЗаписатьЦелое16(28, 0);       // extra field length
	Буфер.Записать(30, ПолучитьБуферДвоичныхДанныхИзСтроки("data", "ascii", Ложь));

	Возврат Буфер;

КонецФункции

Функция ZipDD(CRC32, РазмерСжатыхДанных, РазмерНесжатыхДанных)

	// Data descriptor
	Буфер = Новый БуферДвоичныхДанных(ZipРазмерDD());
	Буфер.ЗаписатьЦелое32(0, 134695760);
	Буфер.ЗаписатьЦелое32(4, CRC32);
	Буфер.ЗаписатьЦелое32(8, РазмерСжатыхДанных);
	Буфер.ЗаписатьЦелое32(12, РазмерНесжатыхДанных);

	Возврат Буфер;

КонецФункции

Функция ZipCDH(CRC32, РазмерСжатыхДанных, РазмерНесжатыхДанных)

	// Central directory header
	Буфер = Новый БуферДвоичныхДанных(ZipРазмерCDH());
	Буфер.ЗаписатьЦелое32(0, 33639248);              // signature 0x02014b50
	Буфер.ЗаписатьЦелое16(4, 798);                   // version made by
	Буфер.ЗаписатьЦелое16(6, 20);                    // version needed to extract
	Буфер.ЗаписатьЦелое16(8, 10);                    // bit flags
	Буфер.ЗаписатьЦелое16(10, 8);                    // compression method
	Буфер.ЗаписатьЦелое16(12, 0);                    // time
	Буфер.ЗаписатьЦелое16(14, 0);                    // date
	Буфер.ЗаписатьЦелое32(16, CRC32);                // crc-32
	Буфер.ЗаписатьЦелое32(20, РазмерСжатыхДанных);   // compressed size
	Буфер.ЗаписатьЦелое32(24, РазмерНесжатыхДанных); // uncompressed size
	Буфер.ЗаписатьЦелое16(28, 4);                    // file name length
	Буфер.ЗаписатьЦелое16(30, 0);                    // extra field length
	Буфер.ЗаписатьЦелое16(32, 0);                    // file comment length
	Буфер.ЗаписатьЦелое16(34, 0);                    // disk number start
	Буфер.ЗаписатьЦелое16(36, 0);                    // internal file attributes
	Буфер.ЗаписатьЦелое32(38, 2176057344);           // external file attributes
	Буфер.ЗаписатьЦелое32(42, 0);                    // relative offset of local header
	Буфер.Записать(46, ПолучитьБуферДвоичныхДанныхИзСтроки("data", "ascii", Ложь));

	Возврат Буфер;

КонецФункции

Функция ZipEOCD(РазмерСжатыхДанных)

	// End of central directory
	РазмерCDH = 50;
	Буфер = Новый БуферДвоичныхДанных(ZipРазмерEOCD());
	Буфер.ЗаписатьЦелое32(0, 101010256); // signature 0x06054b50
	Буфер.ЗаписатьЦелое16(4, 0); // number of this disk
	Буфер.ЗаписатьЦелое16(6, 0); // number of the disk with the start of the central directory
	Буфер.ЗаписатьЦелое16(8, 1); // total number of entries in the central directory on this disk
	Буфер.ЗаписатьЦелое16(10, 1); // total number of entries in the central directory
	Буфер.ЗаписатьЦелое32(12, РазмерCDH); // size of the central directory
	// offset of start of central directory with respect to the starting disk number
	Буфер.ЗаписатьЦелое32(16, ZipРазмерLFH() + РазмерСжатыхДанных + ZipРазмерDD());
	Буфер.ЗаписатьЦелое16(20, 0); // the starting disk number

	Возврат Буфер;

КонецФункции

#КонецОбласти

#Область СлужебныеСтруктурыGZip

// Описание структур см. здесь https://www.ietf.org/rfc/rfc1952.txt

Функция GZipРазмерHeader()

	Возврат 10;

КонецФункции

Функция GZipРазмерFooter()

	Возврат 8;

КонецФункции

Функция GZipHeader()

	Буфер = Новый БуферДвоичныхДанных(GZipРазмерHeader());
	Буфер[0] = 31;               // ID1 0x1f
	Буфер[1] = 139;              // ID2 0x8b
	Буфер[2] = 8;                // compression method (08 for DEFLATE)
	Буфер[3] = 0;                // header flags
	Буфер.ЗаписатьЦелое32(4, 0); // timestamp
	Буфер[8] = 0;                // compression flags
	Буфер[9] = 255;              // operating system ID

	Возврат Буфер;

КонецФункции

Функция GZipFooter(CRC32, РазмерИсходныхДанных)

	Буфер = Новый БуферДвоичныхДанных(GZipРазмерFooter());
	Буфер.ЗаписатьЦелое32(0, CRC32);
	Буфер.ЗаписатьЦелое32(4, РазмерИсходныхДанных);

	Возврат Буфер;

КонецФункции

#КонецОбласти

Функция ПрочитатьZip(СжатыеДанные, ТекстОшибки = Неопределено)

#Если МобильноеПриложениеСервер Тогда
	ВызватьИсключение(НСтр("ru = 'Работа с Zip-файлами в мобильной платформе не поддерживается'"));
#Иначе
	Каталог = ПолучитьИмяВременногоФайла();
	ЧтениеZip = Новый ЧтениеZipФайла(СжатыеДанные);
	ИмяФайла = ЧтениеZip.Элементы[0].Имя;
	Попытка
		ЧтениеZip.Извлечь(ЧтениеZip.Элементы[0], Каталог, РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
	Исключение
		// Игнорируем проверку целостности архива, просто читаем результат
		ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
	КонецПопытки;
	ЧтениеZip.Закрыть();

	Результат = Новый ДвоичныеДанные(Каталог + ПолучитьРазделительПути() + ИмяФайла);
	УдалитьФайлы(Каталог);

	Возврат Результат;
#КонецЕсли

КонецФункции

Функция ЗаписатьZip(Данные)

#Если МобильноеПриложениеСервер Тогда
	ВызватьИсключение(НСтр("ru = 'Работа с Zip-файлами в мобильной платформе не поддерживается'"));
#Иначе
	ВременныйФайл = ПолучитьИмяВременногоФайла(".bin");
	Данные.Записать(ВременныйФайл);
	ПотокZip = Новый ПотокВПамяти;
	ЗаписьZip = Новый ЗаписьZipФайла(ПотокZip);
	ЗаписьZip.Добавить(ВременныйФайл);
	ЗаписьZip.Записать();
	УдалитьФайлы(ВременныйФайл);

	Возврат ПотокZip.ЗакрытьИПолучитьДвоичныеДанные();
#КонецЕсли

КонецФункции

#КонецОбласти

#Область ПараметрыПоУмолчанию

Функция ЗаголовкиПоУмолчанию()

	Заголовки = Новый Соответствие;
#Если МобильноеПриложениеСервер Тогда
	Заголовки.Вставить("Accept-Encoding", "identity");
#Иначе
	Заголовки.Вставить("Accept-Encoding", "gzip");
#КонецЕсли
	Заголовки.Вставить("Accept", "*/*");
	Заголовки.Вставить("Connection", "keep-alive");

	Возврат Заголовки;

КонецФункции

Функция МаксимальноеКоличествоПеренаправлений()

	Возврат 30;

КонецФункции

Функция СтандартныйТаймаут()

	Возврат 30;

КонецФункции

Функция ПараметрыПреобразованияJSONПоУмолчанию()

	ПараметрыПреобразованияПоУмолчанию = Новый Структура;
	ПараметрыПреобразованияПоУмолчанию.Вставить("ПрочитатьВСоответствие", Истина);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ФорматДатыJSON", ФорматДатыJSON.ISO);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ИменаСвойствСоЗначениямиДата", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ВариантЗаписиДатыJSON", ВариантЗаписиДатыJSON.ЛокальнаяДата);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ИмяФункцииПреобразования", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("МодульФункцииПреобразования", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ДополнительныеПараметрыФункцииПреобразования", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ИмяФункцииВосстановления", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("МодульФункцииВосстановления", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ДополнительныеПараметрыФункцииВосстановления", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("ИменаСвойствДляОбработкиВосстановления", Неопределено);
	ПараметрыПреобразованияПоУмолчанию.Вставить("МаксимальнаяВложенность", 500);

	Возврат ПараметрыПреобразованияПоУмолчанию;

КонецФункции

Функция ПараметрыЗаписиJSONПоУмолчанию()

	ПараметрыЗаписиJSONПоУмолчанию = Новый Структура;
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ПереносСтрок", ПереносСтрокJSON.Авто);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("СимволыОтступа", " ");
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ИспользоватьДвойныеКавычки", Истина);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранированиеСимволов", ЭкранированиеСимволовJSON.Нет);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранироватьУгловыеСкобки", Ложь);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранироватьРазделителиСтрок", Истина);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранироватьАмперсанд", Ложь);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранироватьОдинарныеКавычки", Ложь);
	ПараметрыЗаписиJSONПоУмолчанию.Вставить("ЭкранироватьСлеш", Ложь);

	Возврат ПараметрыЗаписиJSONПоУмолчанию;

КонецФункции

#КонецОбласти

#Область КодыСостояний

Функция ОписанияКодовСостоянийHTTP()

	Коды = Новый Массив;
	Коды.Добавить(НовыйКодHTTP(100, "Продолжай_100", "Continue"));
	Коды.Добавить(НовыйКодHTTP(101, "ПереключениеПротокола_101", "Switching Protocols"));
	Коды.Добавить(НовыйКодHTTP(102, "ИдетОбработка_102", "Processing"));
	Коды.Добавить(НовыйКодHTTP(103, "РанняяМетаинформация_103", "Early Hints"));

	Коды.Добавить(НовыйКодHTTP(200, "ОК_200", "OK"));
	Коды.Добавить(НовыйКодHTTP(201, "Создано_201", "Created"));
	Коды.Добавить(НовыйКодHTTP(202, "Принято_202", "Accepted"));
	Коды.Добавить(НовыйКодHTTP(203, "ИнформацияНеАвторитетна_203", "Non-Authoritative Information"));
	Коды.Добавить(НовыйКодHTTP(204, "НетСодержимого_204", "No Content"));
	Коды.Добавить(НовыйКодHTTP(205, "СброситьСодержимое_205", "Reset Content"));
	Коды.Добавить(НовыйКодHTTP(206, "ЧастичноеСодержимое_206", "Partial Content"));
	Коды.Добавить(НовыйКодHTTP(207, "Многостатусный_207", "Multi-Status"));
	Коды.Добавить(НовыйКодHTTP(208, "УжеСообщалось_208", "Already Reported"));
	Коды.Добавить(НовыйКодHTTP(226, "ИспользованоIM_226", "IM Used"));

	Коды.Добавить(НовыйКодHTTP(300, "МножествоВыборов_300", "Multiple Choices"));
	Коды.Добавить(НовыйКодHTTP(301, "ПеремещеноНавсегда_301", "Moved Permanently"));
	Коды.Добавить(НовыйКодHTTP(302, "ПеремещеноВременно_302", "Moved Temporarily"));
	Коды.Добавить(НовыйКодHTTP(303, "СмотретьДругое_303", "See Other"));
	Коды.Добавить(НовыйКодHTTP(304, "НеИзменялось_304", "Not Modified"));
	Коды.Добавить(НовыйКодHTTP(305, "ИспользоватьПрокси_305", "Use Proxy"));
	Коды.Добавить(НовыйКодHTTP(307, "ВременноеПеренаправление_307", "Temporary Redirect"));
	Коды.Добавить(НовыйКодHTTP(308, "ПостоянноеПеренаправление_308", "Permanent Redirect"));

	Коды.Добавить(НовыйКодHTTP(400, "НеверныйЗапрос_400", "Bad Request"));
	Коды.Добавить(НовыйКодHTTP(401, "НеАвторизован_401", "Unauthorized"));
	Коды.Добавить(НовыйКодHTTP(402, "НеобходимаОплата_402", "Payment Required"));
	Коды.Добавить(НовыйКодHTTP(403, "Запрещено_403", "Forbidden"));
	Коды.Добавить(НовыйКодHTTP(404, "НеНайдено_404", "Not Found"));
	Коды.Добавить(НовыйКодHTTP(405, "МетодНеПоддерживается_405", "Method Not Allowed"));
	Коды.Добавить(НовыйКодHTTP(406, "Неприемлемо_406", "Not Acceptable"));
	Коды.Добавить(НовыйКодHTTP(407, "НеобходимаАутентификацияПрокси_407", "Proxy Authentication Required"));
	Коды.Добавить(НовыйКодHTTP(408, "ИстеклоВремяОжидания_408", "Request Timeout"));
	Коды.Добавить(НовыйКодHTTP(409, "Конфликт_409", "Conflict"));
	Коды.Добавить(НовыйКодHTTP(410, "Удален_410", "Gone"));
	Коды.Добавить(НовыйКодHTTP(411, "НеобходимаДлина_411", "Length Required"));
	Коды.Добавить(НовыйКодHTTP(412, "УсловиеЛожно_412", "Precondition Failed"));
	Коды.Добавить(НовыйКодHTTP(413, "ПолезнаяНагрузкаСлишкомВелика_413", "Payload Too Large"));
	Коды.Добавить(НовыйКодHTTP(414, "СлишкомДлинныйURI_414", "URI Too Long"));
	Коды.Добавить(НовыйКодHTTP(415, "НеподдерживаемыйТипДанных_415", "Unsupported Media Type"));
	Коды.Добавить(НовыйКодHTTP(416, "ДиапазонНеДостижим_416", "Range Not Satisfiable"));
	Коды.Добавить(НовыйКодHTTP(417, "ОжиданиеНеУдалось_417", "Expectation Failed"));
	Коды.Добавить(НовыйКодHTTP(419, "ОшибкаПроверкиCSRF_419", "Authentication Timeout"));
	Коды.Добавить(НовыйКодHTTP(421, "НеправильноНаправленныйЗапрос_421", "Misdirected Request"));
	Коды.Добавить(НовыйКодHTTP(422, "НеобрабатываемыйЭкземпляр_422", "Unprocessable Entity"));
	Коды.Добавить(НовыйКодHTTP(423, "Заблокировано_423", "Locked"));
	Коды.Добавить(НовыйКодHTTP(424, "НевыполненнаяЗависимость_424", "Failed Dependency"));
	Коды.Добавить(НовыйКодHTTP(425, "СлишкомРано_425", "Too Early"));
	Коды.Добавить(НовыйКодHTTP(426, "НеобходимоОбновление_426", "Upgrade Required"));
	Коды.Добавить(НовыйКодHTTP(428, "НеобходимоПредусловие_428", "Precondition Required"));
	Коды.Добавить(НовыйКодHTTP(429, "СлишкомМногоЗапросов_429", "Too Many Requests"));
	Коды.Добавить(НовыйКодHTTP(431, "ПоляЗаголовкаЗапросаСлишкомБольшие_431", "Request Header Fields Too Large"));
	Коды.Добавить(НовыйКодHTTP(449, "ПовторитьС_449", "Retry With"));
	Коды.Добавить(НовыйКодHTTP(451, "НедоступноПоЮридическимПричинам_451", "Unavailable For Legal Reasons"));
	Коды.Добавить(НовыйКодHTTP(499, "КлиентЗакрылСоединение_499", "Client Closed Request"));

	Коды.Добавить(НовыйКодHTTP(500, "ВнутренняяОшибкаСервера_500", "Internal Server Error"));
	Коды.Добавить(НовыйКодHTTP(501, "НеРеализовано_501", "Not Implemented"));
	Коды.Добавить(НовыйКодHTTP(502, "ОшибочныйШлюз_502", "Bad Gateway"));
	Коды.Добавить(НовыйКодHTTP(503, "СервисНедоступен_503", "Service Unavailable"));
	Коды.Добавить(НовыйКодHTTP(504, "ШлюзНеОтвечает_504", "Gateway Timeout"));
	Коды.Добавить(НовыйКодHTTP(505, "ВерсияHTTPНеПоддерживается_505", "HTTP Version Not Supported"));
	Коды.Добавить(НовыйКодHTTP(506, "ВариантТожеПроводитСогласование_506", "Variant Also Negotiates"));
	Коды.Добавить(НовыйКодHTTP(507, "ПереполнениеХранилища_507", "Insufficient Storage"));
	Коды.Добавить(НовыйКодHTTP(508, "ОбнаруженоБесконечноеПеренаправление_508", "Loop Detected"));
	Коды.Добавить(НовыйКодHTTP(509, "ИсчерпанаПропускнаяШиринаКанала_509", "Bandwidth Limit Exceeded"));
	Коды.Добавить(НовыйКодHTTP(510, "НеРасширено_510", "Not Extended"));
	Коды.Добавить(НовыйКодHTTP(511, "ТребуетсяСетеваяАутентификация_511", "Network Authentication Required"));
	Коды.Добавить(НовыйКодHTTP(520, "НеизвестнаяОшибка_520", "Unknown Error"));
	Коды.Добавить(НовыйКодHTTP(521, "ВебСерверНеРаботает_521", "Web Server Is Down"));
	Коды.Добавить(НовыйКодHTTP(522, "СоединениеНеОтвечает_522", "Connection Timed Out"));
	Коды.Добавить(НовыйКодHTTP(523, "ИсточникНедоступен_523", "Origin Is Unreachable"));
	Коды.Добавить(НовыйКодHTTP(524, "ВремяОжиданияИстекло_524", "A Timeout Occurred"));
	Коды.Добавить(НовыйКодHTTP(525, "КвитированиеSSНеУдалось_525", "SSL Handshake Failed"));
	Коды.Добавить(НовыйКодHTTP(526, "НедействительныйСертификатSSL_526", "Invalid SSL Certificate"));

	Возврат Коды;

КонецФункции

Функция НовыйКодHTTP(Код, Ключ, Описание)

	Возврат Новый Структура("Код, Ключ, Описание", Код, Ключ, Описание);

КонецФункции

Функция ЭтоКодСостоянияПриКоторомНужноУчитыватьЗаголовокRetryAfter(КодСостояния)

	Коды = КодыСостоянияHTTP();
	Возврат КодСостояния = Коды.ПолезнаяНагрузкаСлишкомВелика_413
		ИЛИ КодСостояния = Коды.СлишкомМногоЗапросов_429
		ИЛИ КодСостояния = Коды.СервисНедоступен_503;

КонецФункции

#КонецОбласти

#Область Прочие

Функция ОпределитьХешФункцию(Знач Алгоритм)

	Алгоритм = ВРег(Алгоритм);
	Если Не ЗначениеЗаполнено(Алгоритм) ИЛИ Алгоритм = "MD5" ИЛИ Алгоритм = "MD5-SESS" Тогда
		АлгоритмХеширования = ХешФункция.MD5;
	ИначеЕсли Алгоритм = "SHA" Тогда
		АлгоритмХеширования = ХешФункция.SHA1;
	ИначеЕсли Алгоритм = "SHA-256" Тогда
		АлгоритмХеширования = ХешФункция.SHA256;
	Иначе
		АлгоритмХеширования = Неопределено;
	КонецЕсли;

	Возврат АлгоритмХеширования;

КонецФункции

Функция ХешированиеДанных(Знач Алгоритм, Знач Данные)

	Если ТипЗнч(Данные) = Тип("Строка") Тогда
		Данные = ПолучитьДвоичныеДанныеИзСтроки(Данные, КодировкаТекста.UTF8, Ложь);
	КонецЕсли;

	Хеширование = Новый ХешированиеДанных(Алгоритм);
	Хеширование.Добавить(Данные);

	Возврат НРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хеширование.ХешСумма));

КонецФункции

Процедура Приостановить(ДлительностьОстановкиВСекундах)

	// Когда-нибудь в платформе сделают паузу и это можно будет выкинуть

	Если ДлительностьОстановкиВСекундах < 1 Тогда
		Возврат;
	КонецЕсли;

	ТекущаяДата = ТекущаяУниверсальнаяДата();
	ЖдатьДо = ТекущаяДата + ДлительностьОстановкиВСекундах;

	// BSLLS:UsingHardcodeNetworkAddress-off
	ЛокальныйХост = "127.0.0.0";
	КакойНибудьСвободныйПорт = 56476;
	// BSLLS:UsingHardcodeNetworkAddress-on
	Пока ТекущаяДата < ЖдатьДо Цикл
		Таймаут = ЖдатьДо - ТекущаяДата;
		Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
		Попытка
			Соединение = Новый HTTPСоединение(
				ЛокальныйХост, КакойНибудьСвободныйПорт, Неопределено, Неопределено, Новый ИнтернетПрокси(Ложь), Таймаут);
			Соединение.Получить(Новый HTTPЗапрос("/does_not_matter"));
		Исключение
			РеальныйТаймаут = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
		КонецПопытки;
		МинимальныйТаймаутВМиллисекундах = 1000;
		Если РеальныйТаймаут < МинимальныйТаймаутВМиллисекундах Тогда
			ВызватьИсключение(НСтр("ru = 'Процедура Приостановить не работает должным образом'"));
		КонецЕсли;
		ТекущаяДата = ТекущаяУниверсальнаяДата();
	КонецЦикла;

КонецПроцедуры

Функция РассчитатьДлительностьПриостановки(Повтор, КоэффициентЭкспоненциальнойЗадержки, ЗаголовокRetryAfter, Остаток)

	Если ЗаголовокRetryAfter <> Ложь Тогда
		Длительность = ЧислоИзСтроки(ЗаголовокRetryAfter);

		Если Длительность = 0 Тогда
			Дата = ДатаИзСтрокиRFC7231(ЗаголовокRetryAfter);
			Если ЗначениеЗаполнено(Дата) Тогда
				Длительность = Дата - ТекущаяУниверсальнаяДата();
			КонецЕсли;
		КонецЕсли;
	Иначе
		Длительность = КоэффициентЭкспоненциальнойЗадержки * Pow(2, Повтор - 1);
	КонецЕсли;

	Длительность = Мин(Длительность, Остаток);

	Если Длительность < 0 Тогда
		Длительность = 0;
	КонецЕсли;

	Возврат Длительность;

КонецФункции

#КонецОбласти

#Область УниверсальныеСтруктурыДанных

Функция ВыбратьЗначение(ОсновноеЗначение, ДополнительныеЗначения, Ключ, ЗначениеПоУмолчанию)

	Если ОсновноеЗначение <> Неопределено Тогда
		Возврат ОсновноеЗначение;
	КонецЕсли;

	Значение = ЗначениеПоКлючу(ДополнительныеЗначения, Ключ);
	Если Значение <> Неопределено Тогда
		Возврат Значение;
	КонецЕсли;

	Возврат ЗначениеПоУмолчанию;

КонецФункции

Функция ЗначениеПоКлючу(Структура, Ключ, ЗначениеПоУмолчанию = Неопределено)

	Если ТипЗнч(Структура) = Тип("Структура") И Структура.Свойство(Ключ) Тогда
		Значение = Структура[Ключ];
	ИначеЕсли ТипЗнч(Структура) = Тип("Соответствие") И Структура.Получить(Ключ) <> Неопределено Тогда
		Значение = Структура.Получить(Ключ);
	Иначе
		Значение = ЗначениеПоУмолчанию;
	КонецЕсли;

	Возврат Значение;

КонецФункции

#КонецОбласти

#Область РаботаСоСтроками

Функция ЧислоИзСтроки(Знач Строка) Экспорт

	ОписаниеТипа = Новый ОписаниеТипов("Число");
	Возврат ОписаниеТипа.ПривестиЗначение(Строка);

КонецФункции

Функция ДатаИзСтроки(Знач Строка) Экспорт

	КвалификаторДаты = Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя);
	ОписаниеТипа = Новый ОписаниеТипов("Дата", Неопределено, Неопределено, КвалификаторДаты);
	Возврат ОписаниеТипа.ПривестиЗначение(Строка);

КонецФункции

Функция ДатаИзСтрокиRFC7231(Знач Строка) Экспорт

	Разделители = ",-:/\.";
	Для Индекс = 1 По СтрДлина(Разделители) Цикл
		Разделитель = Сред(Разделители, Индекс, 1);
		Строка = СтрЗаменить(Строка, Разделитель, " ");
	КонецЦикла;
	Строка = СтрЗаменить(Строка, "  ", " ");
	СоставляющиеДаты = СтрРазделить(Строка, " ");
	МесяцСтр = СоставляющиеДаты[2];

	Месяцы = СтрРазделить("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",");
	Месяц = Месяцы.Найти(МесяцСтр);
	Если Месяц = Неопределено Тогда
		Возврат '00010101';
	КонецЕсли;

	Дата = СоставляющиеДаты[3] + Формат(Месяц + 1, "ЧЦ=2; ЧВН=;") + СоставляющиеДаты[1];
	Время = СоставляющиеДаты[4] + СоставляющиеДаты[5] + СоставляющиеДаты[6];

	Возврат ДатаИзСтроки(Дата + Время);

КонецФункции

Процедура РазбитьСтрокуПоРазделителю(ИзвлекаемаяЧасть, ОстальнаяЧасть, Разделитель, Инверсия = Ложь)

	Индекс = СтрНайти(ОстальнаяЧасть, Разделитель);
	Если Индекс Тогда
		ИзвлекаемаяЧасть = Лев(ОстальнаяЧасть, Индекс - 1);
		ОстальнаяЧасть = Сред(ОстальнаяЧасть, Индекс + СтрДлина(Разделитель));
		Если Инверсия Тогда
			ДляОбмена = ИзвлекаемаяЧасть;
			ИзвлекаемаяЧасть = ОстальнаяЧасть;
			ОстальнаяЧасть = ДляОбмена;
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

Функция РазделитьПоПервомуНайденномуРазделителю(Строка, Разделители)

	МинимальныйИндекс = СтрДлина(Строка);
	ПервыйРазделитель = "";

	Для Каждого Разделитель Из Разделители Цикл
		Индекс = СтрНайти(Строка, Разделитель);
		Если Индекс = 0 Тогда
			Продолжить;
		КонецЕсли;
		Если Индекс < МинимальныйИндекс Тогда
			МинимальныйИндекс = Индекс;
			ПервыйРазделитель = Разделитель;
		КонецЕсли;
	КонецЦикла;

	Результат = Новый Массив;
	Если ЗначениеЗаполнено(ПервыйРазделитель) Тогда
		Результат.Добавить(Лев(Строка, МинимальныйИндекс - 1));
		Результат.Добавить(Сред(Строка, МинимальныйИндекс + СтрДлина(ПервыйРазделитель)));
		Результат.Добавить(ПервыйРазделитель);
	Иначе
		Результат.Добавить(Строка);
		Результат.Добавить("");
		Результат.Добавить(Неопределено);
	КонецЕсли;

	Возврат Результат;

КонецФункции

Функция РазбитьСтрокуПоСтроке(Знач Строка, Разделитель)

	Результат = Новый Массив;
	Пока Истина Цикл
		Позиция = СтрНайти(Строка, Разделитель);
		Если Позиция = 0 И ЗначениеЗаполнено(Строка) Тогда
			Результат.Добавить(Строка);
			Прервать;
		КонецЕсли;

		ПерваяЧасть = Лев(Строка, Позиция - СтрДлина(Разделитель) + 1);
		Результат.Добавить(ПерваяЧасть);
		Строка = Сред(Строка, Позиция + СтрДлина(Разделитель));
	КонецЦикла;

	Возврат Результат;

КонецФункции

Функция ДобавитьЛидирующуюТочку(Знач Домен)

	Если Не СтрНачинаетсяС(Домен, ".") Тогда
		Домен = "." + Домен;
	КонецЕсли;

	Возврат Домен;

КонецФункции

Функция ВырезатьТекст(Текст, МаксимальнаяДлинаТекста = 1000)
	
	Если НайтиНедопустимыеСимволыXML(Текст) Тогда
		Возврат НСтр("ru ='<Данные>'");
	КонецЕсли;
	
	Если СтрДлина(Текст) <= МаксимальнаяДлинаТекста Тогда
		Результат = Текст;
	Иначе
		ПоловинаМаксимальнойДлиныТекста = МаксимальнаяДлинаТекста / 2;
		Результат = Лев(Текст, ПоловинаМаксимальнойДлиныТекста);
		Результат = Результат + Символы.ПС + "..." + Символы.ПС;
		Результат = Результат + Прав(Текст, ПоловинаМаксимальнойДлиныТекста);
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

Функция Объединить(ГлавныйИсточник, ДополнительныйИсточник)

	Результат = ГлавныйИсточник;
	Дополнить(ГлавныйИсточник, ДополнительныйИсточник);
	Возврат Результат;

КонецФункции

Функция Скопировать(Источник)

	#Если МобильноеПриложениеСервер Тогда
		Возврат JsonВОбъект(ОбъектВJson(Источник));
	#Иначе
		Возврат ЗначениеИзСтрокиВнутр(ЗначениеВСтрокуВнутр(Источник));
	#КонецЕсли

КонецФункции

Процедура Дополнить(Приемник, Источник)
	
	Если Источник = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Для Каждого ЭлементИсточника Из Источник Цикл
		ПараметрНайден = Ложь;
		
		Если ТипЗнч(Приемник) = Тип("Соответствие") Тогда
			ПараметрНайден = Приемник.Получить(ЭлементИсточника.Ключ) <> Неопределено;
		КонецЕсли;
		
		Если ТипЗнч(Приемник) = Тип("Структура") Тогда
			ПараметрНайден = Приемник.Свойство(ЭлементИсточника.Ключ);
		КонецЕсли;
		
		Если Не ПараметрНайден ИЛИ ПараметрНайден И ЭлементИсточника.Значение <> Неопределено Тогда
			Приемник.Вставить(ЭлементИсточника.Ключ, ЭлементИсточника.Значение);
		КонецЕсли;
	КонецЦикла;
		
КонецПроцедуры

#КонецОбласти

#КонецОбласти
